//
//  SuperTuxKart - a fun racing game with go-kart
//  Copyright (C) 2012-2015 SuperTuxKart-Team
//
//  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 3
//  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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

#ifdef ENABLE_WIIUSE
#define WCONST

#include "input/wiimote_manager.hpp"

#include "graphics/irr_driver.hpp"
#include "guiengine/modaldialog.hpp"
#include "gamepad_device.hpp"
#include "input/input_manager.hpp"
#include "input/device_manager.hpp"
#include "input/wiimote.hpp"
#include "utils/string_utils.hpp"
#include "utils/time.hpp"
#include "utils/translation.hpp"
#include "utils/vs.hpp"

#include "wiiuse.h"

#include <functional>

WiimoteManager*  wiimote_manager;


bool WiimoteManager::m_enabled      = false;

/** Irrlicht device IDs for the wiimotes start at this value */
static const int    WIIMOTE_START_IRR_ID   = 32;

WiimoteManager::WiimoteManager()
{
    m_all_wiimote_handles = NULL;
#ifdef WIIMOTE_THREADING
    m_shut = false;
#endif
}   // WiimoteManager

// -----------------------------------------------------------------------------
WiimoteManager::~WiimoteManager()
{
    cleanup();
}   // ~WiimoteManager

// -----------------------------------------------------------------------------
/**
 * Launch wiimote detection and add the corresponding gamepad devices to the
 * device manager.
 * TODO: this should be done in a separate thread, to not block the UI...
 */
void WiimoteManager::launchDetection(int timeout)
{
  // It's only needed on systems with bluez, because wiiuse_find does not find alredy connected wiimotes
#ifdef WIIUSE_BLUEZ
    //Cleans up the config and the disconnected wiimotes
    int number_previous_wiimotes = 0;
    wiimote_t** previous_wiimotes = (wiimote_t**) malloc(sizeof(struct wiimote_t*) * MAX_WIIMOTES);
    memset(previous_wiimotes,0,sizeof(struct wiimote_t*) * MAX_WIIMOTES);
    for (unsigned int i = 0; i < m_wiimotes.size(); i++)
    {
      if (WIIMOTE_IS_CONNECTED(m_all_wiimote_handles[i]))
      {
        previous_wiimotes[i]=m_all_wiimote_handles[i];
        m_all_wiimote_handles[i] = NULL;
        number_previous_wiimotes++;
      }
    }

    //To prevent segmentation fault, have to delete NULLs
    wiimote_t** deletable_wiimotes = (wiimote_t**) malloc(sizeof(struct wiimote_t*) * (m_wiimotes.size()-number_previous_wiimotes));
    memset(deletable_wiimotes,0,sizeof(struct wiimote_t*) * (m_wiimotes.size()-number_previous_wiimotes));
    int number_deletables = 0;
    for (unsigned int i = 0; i < m_wiimotes.size(); i++)
    {
      if (m_all_wiimote_handles[i] != NULL)
      {
        deletable_wiimotes[number_deletables++] = m_all_wiimote_handles[i];
      }
    }
    m_all_wiimote_handles = wiiuse_init(MAX_WIIMOTES);
    wiiuse_cleanup(deletable_wiimotes, number_deletables);

#endif

    // Stop WiiUse, remove wiimotes, gamepads, gamepad configs.
    cleanup();

    m_all_wiimote_handles = wiiuse_init(MAX_WIIMOTES);

    // Detect wiimotes
    int nb_found_wiimotes = wiiuse_find(m_all_wiimote_handles, MAX_WIIMOTES, timeout);

#ifndef WIIUSE_BLUEZ
    // Couldn't find any wiimote?
    if(nb_found_wiimotes == 0)
        return;
#endif

#ifdef WIIUSE_BLUEZ
    // Couldn't find any wiimote?
    if(nb_found_wiimotes + number_previous_wiimotes == 0)
        return;
#endif

    // Try to connect to all found wiimotes
    int nb_wiimotes = wiiuse_connect(m_all_wiimote_handles, nb_found_wiimotes);

#ifndef WIIUSE_BLUEZ
    // Couldn't connect to any wiimote?
    if(nb_wiimotes == 0)
        return;
#endif

#ifdef WIIUSE_BLUEZ
    // Couldn't connect to any wiimote?
    if(nb_wiimotes + number_previous_wiimotes == 0)
        return;

    //Merges previous and new wiimote's list
    int number_merged_wiimotes = 0;
    for (int i = 0; i < number_previous_wiimotes && i + nb_wiimotes < MAX_WIIMOTES; i++)
    {
      m_all_wiimote_handles[i+nb_wiimotes] = previous_wiimotes[i];
      previous_wiimotes[i] = NULL;
      m_all_wiimote_handles[i]->unid = nb_wiimotes+i+1;
      number_merged_wiimotes++;
    }
    nb_wiimotes += number_merged_wiimotes;

    //To prevent segmentation fault, have to delete NULLs
    number_deletables = 0;
    deletable_wiimotes = (wiimote_t**) malloc(sizeof(struct wiimote_t*) * (number_previous_wiimotes-number_merged_wiimotes));
    memset(deletable_wiimotes,0,sizeof(struct wiimote_t*) * (number_previous_wiimotes-number_merged_wiimotes));
    for (int i = 0; i < number_previous_wiimotes; i++)
    {
      if (previous_wiimotes[i] != NULL)
      {
        deletable_wiimotes[number_deletables++] = previous_wiimotes[i];
      }
    }
    // Cleans up wiimotes which ones didn't fit in limit
    wiiuse_cleanup(deletable_wiimotes, number_deletables);

#endif

    // ---------------------------------------------------
    // Create or find a GamepadConfig for all wiimotes
    DeviceManager* device_manager = input_manager->getDeviceManager();
    GamepadConfig* gamepad_config = NULL;

    device_manager->getConfigForGamepad(WIIMOTE_START_IRR_ID, "Wiimote",
                                        &gamepad_config);
    int num_buttons = (int)( log((float)WIIMOTE_BUTTON_ALL) / log((float)2.0f))+1;
    gamepad_config->setNumberOfButtons(num_buttons);
    gamepad_config->setNumberOfAxis(1);

    setWiimoteBindings(gamepad_config);

    // Initialize all Wiimotes, which in turn create their
    // associated GamePadDevices
    for(int i=0 ; i < nb_wiimotes ; i++)
    {
        m_wiimotes.push_back(new Wiimote(m_all_wiimote_handles[i], i,
                                         gamepad_config              ));
    } // end for

    // ---------------------------------------------------
    // Set the LEDs and rumble for 0.2s
    int leds[] = {WIIMOTE_LED_1, WIIMOTE_LED_2, WIIMOTE_LED_3, WIIMOTE_LED_4};
    for(unsigned int i=0 ; i < m_wiimotes.size(); i++)
    {
        wiimote_t*  wiimote_handle = m_wiimotes[i]->getWiimoteHandle();
        wiiuse_set_leds(wiimote_handle, leds[i]);
        wiiuse_rumble(wiimote_handle, 1);
    }

    StkTime::sleep(200);

    for(unsigned int i=0 ; i < m_wiimotes.size(); i++)
    {
        wiimote_t*  wiimote_handle = m_wiimotes[i]->getWiimoteHandle();
        wiiuse_rumble(wiimote_handle, 0);
    }

    // TODO: only enable accelerometer during race
    enableAccelerometer(true);

    // ---------------------------------------------------
    // Launch the update thread
#ifdef WIIMOTE_THREADING
    m_shut = false;
    m_thread = std::thread(std::bind(&WiimoteManager::threadFunc, this));
#endif
}   // launchDetection

// ----------------------------------------------------------------------------
/** Determines the button number based on the bitmask of a button. E.g.
 *  0x0004 (= 00100_2) is converted into 3.
 */
int getButton(int n)
{
    return (int)(log((float)n)/log((float)2.0f));
}   // getButton

// ----------------------------------------------------------------------------
/** Defines the key bindings for a wiimote for the device manager.
 *  \param gamepad_config The configuration to be defined.
 */
void WiimoteManager::setWiimoteBindings(GamepadConfig* gamepad_config)
{
    gamepad_config->setBinding(PA_STEER_LEFT,   Input::IT_STICKMOTION, 0, Input::AD_NEGATIVE);
    gamepad_config->setBinding(PA_STEER_RIGHT,  Input::IT_STICKMOTION, 0, Input::AD_POSITIVE);
    gamepad_config->setBinding(PA_ACCEL,        Input::IT_STICKBUTTON, getButton(WIIMOTE_BUTTON_TWO));
    gamepad_config->setBinding(PA_BRAKE,        Input::IT_STICKBUTTON, getButton(WIIMOTE_BUTTON_ONE));
    gamepad_config->setBinding(PA_FIRE,         Input::IT_STICKBUTTON, getButton(WIIMOTE_BUTTON_RIGHT));
    gamepad_config->setBinding(PA_NITRO,        Input::IT_STICKBUTTON, getButton(WIIMOTE_BUTTON_UP));
    gamepad_config->setBinding(PA_DRIFT,        Input::IT_STICKBUTTON, getButton(WIIMOTE_BUTTON_B));
    gamepad_config->setBinding(PA_RESCUE,       Input::IT_STICKBUTTON, getButton(WIIMOTE_BUTTON_PLUS));
    gamepad_config->setBinding(PA_LOOK_BACK,    Input::IT_STICKBUTTON, getButton(WIIMOTE_BUTTON_DOWN));
    gamepad_config->setBinding(PA_PAUSE_RACE,   Input::IT_STICKBUTTON, getButton(WIIMOTE_BUTTON_HOME));

    gamepad_config->setBinding(PA_MENU_UP,      Input::IT_STICKBUTTON, getButton(WIIMOTE_BUTTON_RIGHT));
    gamepad_config->setBinding(PA_MENU_DOWN,    Input::IT_STICKBUTTON, getButton(WIIMOTE_BUTTON_LEFT));
    gamepad_config->setBinding(PA_MENU_LEFT,    Input::IT_STICKBUTTON, getButton(WIIMOTE_BUTTON_UP));
    gamepad_config->setBinding(PA_MENU_RIGHT,   Input::IT_STICKBUTTON, getButton(WIIMOTE_BUTTON_DOWN));
    gamepad_config->setBinding(PA_MENU_SELECT,  Input::IT_STICKBUTTON, getButton(WIIMOTE_BUTTON_TWO));
    gamepad_config->setBinding(PA_MENU_CANCEL,  Input::IT_STICKBUTTON, getButton(WIIMOTE_BUTTON_ONE));
}   // setWiimoteBindings

// ----------------------------------------------------------------------------
void WiimoteManager::cleanup()
{
    if(m_wiimotes.size() > 0)
    {
        DeviceManager* device_manager = input_manager->getDeviceManager();

        GamePadDevice* first_gamepad_device =
                     device_manager->getGamePadFromIrrID(WIIMOTE_START_IRR_ID);
        assert(first_gamepad_device);

        DeviceConfig*  gamepad_config =
                                      first_gamepad_device->getConfiguration();
        assert(gamepad_config);

        // Remove the wiimote configuration -> automatically removes all
        // linked gamepad devices;
        device_manager->deleteConfig(gamepad_config);

        // Shut the update thread
#ifdef WIIMOTE_THREADING
        m_shut = true;
        m_thread.join();
#endif
        // Cleanup WiiUse
        wiiuse_cleanup(m_all_wiimote_handles, MAX_WIIMOTES);
    }

    for(unsigned int i=0; i<m_wiimotes.size(); i++)
        delete m_wiimotes[i];
    m_wiimotes.clear();

    // Reset
    m_all_wiimote_handles = NULL;
#ifdef WIIMOTE_THREADING
    m_shut                = false;
#endif
}   // cleanup

// ----------------------------------------------------------------------------
void WiimoteManager::update()
{
#ifndef WIIMOTE_THREADING
    threadFunc();
#endif
    for(unsigned int i=0 ; i < m_wiimotes.size(); i++)
    {
        irr::SEvent event = m_wiimotes[i]->getIrrEvent();
        input_manager->input(event);
    }
}   // update

// ----------------------------------------------------------------------------
/** Enables or disables the accelerometer in wiimotes (to save battery life).
 *  \param state True if the accelerometer should be enabled.
 */
void WiimoteManager::enableAccelerometer(bool state)
{
    for (unsigned int i=0; i < m_wiimotes.size(); ++i)
    {
        wiiuse_motion_sensing(m_wiimotes[i]->getWiimoteHandle(), state ? 1 : 0);
    }
}   // enableAccelerometer

// ----------------------------------------------------------------------------
/** Thread update method - wiimotes state is updated in another thread to
 *  avoid latency problems */
void WiimoteManager::threadFunc()
{
#ifdef WIIMOTE_THREADING
    VS::setThreadName("WiimoteManager");
    while(!m_shut)
#endif
    {
        if(wiiuse_poll(m_all_wiimote_handles, MAX_WIIMOTES))
        {
            for (unsigned int i=0; i < m_wiimotes.size(); ++i)
            {
                switch (m_all_wiimote_handles[i]->event)
                {
                case WIIUSE_EVENT:
                    m_wiimotes[i]->update();
                    //printf("DEBUG: wiimote event\n");
                    break;

                case WIIUSE_STATUS:
                    //printf("DEBUG: status event\n");
                    break;

                case WIIUSE_DISCONNECT:
                case WIIUSE_UNEXPECTED_DISCONNECT:
                    //printf("DEBUG: wiimote disconnected\n");
                    m_wiimotes[i]->setConnected(false);
                    break;

                case WIIUSE_READ_DATA:
                    //printf("DEBUG: WIIUSE_READ_DATA\n");
                    break;

                case WIIUSE_NUNCHUK_INSERTED:
                    //printf("DEBUG: Nunchuk inserted.\n");
                    break;

                case WIIUSE_CLASSIC_CTRL_INSERTED:
                    //printf("DEBUG: Classic controller inserted.\n");
                    break;

                case WIIUSE_GUITAR_HERO_3_CTRL_INSERTED:
                    //printf("DEBUG: Guitar Hero 3 controller inserted.\n");
                    break;

                case WIIUSE_NUNCHUK_REMOVED:
                case WIIUSE_CLASSIC_CTRL_REMOVED:
                case WIIUSE_GUITAR_HERO_3_CTRL_REMOVED:
                    //printf("DEBUG: An expansion was removed.\n");
                    break;
                default:
                    break;
                }
            }
        }

        StkTime::sleep(1);  // 'cause come on, the whole CPU is not ours :)
    } // end while
}   // threadFunc

// ----------------------------------------------------------------------------
/** Shows a simple popup menu asking the user to connect all wiimotes.
 */
int WiimoteManager::askUserToConnectWiimotes()
{
    new MessageDialog(
#ifdef WIN32
        _("Connect your wiimote to the Bluetooth manager, then click on Ok. "
                  "Detailed instructions at https://supertuxkart.net/Wiimote"),
#else
        _("Press the buttons 1+2 simultaneously on your wiimote to put "
          "it in discovery mode, then click on Ok. "
                  "Detailed instructions at https://supertuxkart.net/Wiimote"),
#endif
        MessageDialog::MESSAGE_DIALOG_OK_CANCEL,
        new WiimoteDialogListener(), true);

    return getNumberOfWiimotes();
}   // askUserToConnectWiimotes

// ============================================================================
/** Calles when the user clicks on OK, i.e. all wiimotes are in discovery
 *  mode.
 */
void WiimoteManager::WiimoteDialogListener::onConfirm()
{
    GUIEngine::ModalDialog::dismiss();

    wiimote_manager->launchDetection(5);

    int nb_wiimotes = wiimote_manager->getNumberOfWiimotes();
    if(nb_wiimotes > 0)
    {
        new MessageDialog(_P("Found %d wiimote", "Found %d wiimotes",
                             nb_wiimotes));
    }
    else
    {
        new MessageDialog( _("Could not detect any wiimote :/") );
    }
}   // WiimoteDialogListeneronConfirm
#endif // ENABLE_WIIUSE
