/*
The zlib/libpng License

Copyright (c) 2018 Arthur Brainville
Copyright (c) 2015 Andrew Fenn
Copyright (c) 2005-2010 Phillip Castaneda (pjcast -- www.wreckedgames.com)

This software is provided 'as-is', without any express or implied warranty. In no
event will the authors be held liable for any damages arising from the use of this
software.

Permission is granted to anyone to use this software for any purpose, including
commercial applications, and to alter it and redistribute it freely, subject to the
following restrictions:

    1. The origin of this software must not be misrepresented; you must not claim that
        you wrote the original software. If you use this software in a product,
        an acknowledgment in the product documentation would be appreciated
        but is not required.

    2. Altered source versions must be plainly marked as such, and must not be
        misrepresented as being the original software.

    3. This notice may not be removed or altered from any source distribution.   
*/
#include "linux/LinuxForceFeedback.h"
#include "OISException.h"

#include <cstdlib>
#include <errno.h>
#include <memory.h>

using namespace OIS;

// 0 = No trace; 1 = Important traces; 2 = Debug traces
#define OIS_LINUX_JOYFF_DEBUG 1

#ifdef OIS_LINUX_JOYFF_DEBUG
#include <iostream>
using namespace std;
#endif

//--------------------------------------------------------------//
LinuxForceFeedback::LinuxForceFeedback(int deviceID) :
 ForceFeedback(), mJoyStick(deviceID)
{
}

//--------------------------------------------------------------//
LinuxForceFeedback::~LinuxForceFeedback()
{
	// Unload all effects.
	for(EffectList::iterator i = mEffectList.begin(); i != mEffectList.end(); ++i)
	{
		struct ff_effect* linEffect = i->second;
		if(linEffect)
			_unload(linEffect->id);
	}

	mEffectList.clear();
}

//--------------------------------------------------------------//
unsigned short LinuxForceFeedback::getFFMemoryLoad()
{
	int nEffects = -1;
	if(ioctl(mJoyStick, EVIOCGEFFECTS, &nEffects) == -1)
		OIS_EXCEPT(E_General, "Unknown error reading max number of uploaded effects.");
#if(OIS_LINUX_JOYFF_DEBUG > 1)
	cout << "LinuxForceFeedback(" << mJoyStick
		 << ") : Read device max number of uploaded effects : " << nEffects << endl;
#endif

	return (unsigned short int)(nEffects > 0 ? 100.0 * mEffectList.size() / nEffects : 100);
}

//--------------------------------------------------------------//
void LinuxForceFeedback::setMasterGain(float value)
{
	if(!mSetGainSupport)
	{
#if(OIS_LINUX_JOYFF_DEBUG > 0)
		cout << "LinuxForceFeedback(" << mJoyStick << ") : Setting master gain "
			 << "is not supported by the device" << endl;
#endif
		return;
	}

	struct input_event event;

	memset(&event, 0, sizeof(event));
	event.type = EV_FF;
	event.code = FF_GAIN;
	if(value < 0.0)
		value = 0.0;
	else if(value > 1.0)
		value = 1.0;
	event.value = (__s32)(value * 0xFFFFUL);

#if(OIS_LINUX_JOYFF_DEBUG > 0)
	cout << "LinuxForceFeedback(" << mJoyStick << ") : Setting master gain to "
		 << value << " => " << event.value << endl;
#endif

	if(write(mJoyStick, &event, sizeof(event)) != sizeof(event))
	{
		OIS_EXCEPT(E_General, "Unknown error changing master gain.");
	}
}

//--------------------------------------------------------------//
void LinuxForceFeedback::setAutoCenterMode(bool enabled)
{
	if(!mSetAutoCenterSupport)
	{
#if(OIS_LINUX_JOYFF_DEBUG > 0)
		cout << "LinuxForceFeedback(" << mJoyStick << ") : Setting auto-center mode "
			 << "is not supported by the device" << endl;
#endif
		return;
	}

	struct input_event event;

	memset(&event, 0, sizeof(event));
	event.type	= EV_FF;
	event.code	= FF_AUTOCENTER;
	event.value = (__s32)(enabled * 0xFFFFFFFFUL);

#if(OIS_LINUX_JOYFF_DEBUG > 0)
	cout << "LinuxForceFeedback(" << mJoyStick << ") : Toggling auto-center to "
		 << enabled << " => 0x" << hex << event.value << dec << endl;
#endif

	if(write(mJoyStick, &event, sizeof(event)) != sizeof(event))
	{
		OIS_EXCEPT(E_General, "Unknown error toggling auto-center.");
	}
}

//--------------------------------------------------------------//
void LinuxForceFeedback::upload(const Effect* effect)
{
	switch(effect->force)
	{
		case OIS::Effect::ConstantForce:
			_updateConstantEffect(effect);
			break;
		case OIS::Effect::ConditionalForce:
			_updateConditionalEffect(effect);
			break;
		case OIS::Effect::PeriodicForce:
			_updatePeriodicEffect(effect);
			break;
		case OIS::Effect::RampForce:
			_updateRampEffect(effect);
			break;
		case OIS::Effect::CustomForce:
			//_updateCustomEffect(effect);
			//break;
		default:
			OIS_EXCEPT(E_NotImplemented, "Requested force not implemented yet, sorry!");
			break;
	}
}

//--------------------------------------------------------------//
void LinuxForceFeedback::modify(const Effect* effect)
{
	upload(effect);
}

//--------------------------------------------------------------//
void LinuxForceFeedback::remove(const Effect* effect)
{
	//Get the effect - if it exists
	EffectList::iterator i = mEffectList.find(effect->_handle);
	if(i != mEffectList.end())
	{
		struct ff_effect* linEffect = i->second;
		if(linEffect)
		{
			_stop(effect->_handle);

			_unload(effect->_handle);

			free(linEffect);

			mEffectList.erase(i);
		}
		else
			mEffectList.erase(i);
	}
}

//--------------------------------------------------------------//
// To Signed16/Unsigned15 safe conversions
#define MaxUnsigned15Value 0x7FFF
#define toUnsigned15(value) \
	(__u16)((value) < 0 ? 0 : ((value) > MaxUnsigned15Value ? MaxUnsigned15Value : (value)))

#define MaxSigned16Value 0x7FFF
#define MinSigned16Value -0x7FFF
#define toSigned16(value) \
	(__s16)((value) < MinSigned16Value ? MinSigned16Value : ((value) > MaxSigned16Value ? MaxSigned16Value : (value)))

// OIS to Linux duration
#define LinuxInfiniteDuration 0xFFFF
#define OISDurationUnitMS 1000 // OIS duration unit (microseconds), expressed in milliseconds (theLinux duration unit)

// linux/input.h : All duration values are expressed in ms. Values above 32767 ms (0x7fff)
//                 should not be used and have unspecified results.
#define LinuxDuration(oisDuration) ((oisDuration) == Effect::OIS_INFINITE ? LinuxInfiniteDuration \
																		  : toUnsigned15((oisDuration) / OISDurationUnitMS))

// OIS to Linux levels
#define OISMaxLevel 10000
#define LinuxMaxLevel 0x7FFF

// linux/input.h : Valid range for the attack and fade levels is 0x0000 - 0x7fff
#define LinuxPositiveLevel(oisLevel) toUnsigned15(LinuxMaxLevel*(long)(oisLevel) / OISMaxLevel)

#define LinuxSignedLevel(oisLevel) toSigned16(LinuxMaxLevel*(long)(oisLevel) / OISMaxLevel)

//--------------------------------------------------------------//
void LinuxForceFeedback::_setCommonProperties(struct ff_effect* event,
											  struct ff_envelope* ffenvelope,
											  const Effect* effect,
											  const Envelope* envelope)
{
	memset(event, 0, sizeof(struct ff_effect));

	if(envelope && ffenvelope && envelope->isUsed())
	{
		ffenvelope->attack_length = LinuxDuration(envelope->attackLength);
		ffenvelope->attack_level  = LinuxPositiveLevel(envelope->attackLevel);
		ffenvelope->fade_length	  = LinuxDuration(envelope->fadeLength);
		ffenvelope->fade_level	  = LinuxPositiveLevel(envelope->fadeLevel);
	}

#if(OIS_LINUX_JOYFF_DEBUG > 1)
	cout << endl;
	if(envelope && ffenvelope)
	{
		cout << "  Enveloppe :" << endl
			 << "    AttackLen : " << envelope->attackLength
			 << " => " << ffenvelope->attack_length << endl
			 << "    AttackLvl : " << envelope->attackLevel
			 << " => " << ffenvelope->attack_level << endl
			 << "    FadeLen   : " << envelope->fadeLength
			 << " => " << ffenvelope->fade_length << endl
			 << "    FadeLvl   : " << envelope->fadeLevel
			 << " => " << ffenvelope->fade_level << endl;
	}
#endif

	event->direction = (__u16)(1 + (effect->direction * 45.0 + 135.0) * 0xFFFFUL / 360.0);

#if(OIS_LINUX_JOYFF_DEBUG > 1)
	cout << "  Direction : " << Effect::getDirectionName(effect->direction)
		 << " => 0x" << hex << event->direction << dec << endl;
#endif

	// TODO trigger_button 0 vs. -1
	event->trigger.button	= effect->trigger_button; // < 0 ? 0 : effect->trigger_button;
	event->trigger.interval = LinuxDuration(effect->trigger_interval);

#if(OIS_LINUX_JOYFF_DEBUG > 1)
	cout << "  Trigger :" << endl
		 << "    Button   : " << effect->trigger_button
		 << " => " << event->trigger.button << endl
		 << "    Interval : " << effect->trigger_interval
		 << " => " << event->trigger.interval << endl;
#endif

	event->replay.length = LinuxDuration(effect->replay_length);
	event->replay.delay	 = LinuxDuration(effect->replay_delay);

#if(OIS_LINUX_JOYFF_DEBUG > 1)
	cout << "  Replay :" << endl
		 << "    Length : " << effect->replay_length
		 << " => " << event->replay.length << endl
		 << "    Delay  : " << effect->replay_delay
		 << " => " << event->replay.delay << endl;
#endif
}

//--------------------------------------------------------------//
void LinuxForceFeedback::_updateConstantEffect(const Effect* eff)
{
	struct ff_effect event;

	ConstantEffect* effect = static_cast<ConstantEffect*>(eff->getForceEffect());

	_setCommonProperties(&event, &event.u.constant.envelope, eff, &effect->envelope);

	event.type = FF_CONSTANT;
	event.id   = -1;

	event.u.constant.level = LinuxSignedLevel(effect->level);

#if(OIS_LINUX_JOYFF_DEBUG > 1)
	cout << "  Level : " << effect->level
		 << " => " << event.u.constant.level << endl;
#endif

	_upload(&event, eff);
}

//--------------------------------------------------------------//
void LinuxForceFeedback::_updateRampEffect(const Effect* eff)
{
	struct ff_effect event;

	RampEffect* effect = static_cast<RampEffect*>(eff->getForceEffect());

	_setCommonProperties(&event, &event.u.constant.envelope, eff, &effect->envelope);

	event.type = FF_RAMP;
	event.id   = -1;

	event.u.ramp.start_level = LinuxSignedLevel(effect->startLevel);
	event.u.ramp.end_level	 = LinuxSignedLevel(effect->endLevel);

#if(OIS_LINUX_JOYFF_DEBUG > 1)
	cout << "  StartLevel : " << effect->startLevel
		 << " => " << event.u.ramp.start_level << endl
		 << "  EndLevel   : " << effect->endLevel
		 << " => " << event.u.ramp.end_level << endl;
#endif

	_upload(&event, eff);
}

//--------------------------------------------------------------//
void LinuxForceFeedback::_updatePeriodicEffect(const Effect* eff)
{
	struct ff_effect event;

	PeriodicEffect* effect = static_cast<PeriodicEffect*>(eff->getForceEffect());

	_setCommonProperties(&event, &event.u.periodic.envelope, eff, &effect->envelope);

	event.type = FF_PERIODIC;
	event.id   = -1;

	switch(eff->type)
	{
		case OIS::Effect::Square:
			event.u.periodic.waveform = FF_SQUARE;
			break;
		case OIS::Effect::Triangle:
			event.u.periodic.waveform = FF_TRIANGLE;
			break;
		case OIS::Effect::Sine:
			event.u.periodic.waveform = FF_SINE;
			break;
		case OIS::Effect::SawToothUp:
			event.u.periodic.waveform = FF_SAW_UP;
			break;
		case OIS::Effect::SawToothDown:
			event.u.periodic.waveform = FF_SAW_DOWN;
			break;
		// Note: No support for Custom periodic force effect for the moment
		//case OIS::Effect::Custom:
		//event.u.periodic.waveform = FF_CUSTOM;
		//break;
		default:
			OIS_EXCEPT(E_General, "No such available effect for Periodic force!");
			break;
	}

	event.u.periodic.period	   = LinuxDuration(effect->period);
	event.u.periodic.magnitude = LinuxPositiveLevel(effect->magnitude);
	event.u.periodic.offset	   = LinuxPositiveLevel(effect->offset);
	event.u.periodic.phase	   = (__u16)(effect->phase * event.u.periodic.period / 36000.0); // ?????

	// Note: No support for Custom periodic force effect for the moment
	event.u.periodic.custom_len	 = 0;
	event.u.periodic.custom_data = 0;

#if(OIS_LINUX_JOYFF_DEBUG > 1)
	cout << "  Magnitude : " << effect->magnitude
		 << " => " << event.u.periodic.magnitude << endl
		 << "  Period    : " << effect->period
		 << " => " << event.u.periodic.period << endl
		 << "  Offset    : " << effect->offset
		 << " => " << event.u.periodic.offset << endl
		 << "  Phase     : " << effect->phase
		 << " => " << event.u.periodic.phase << endl;
#endif

	_upload(&event, eff);
}

//--------------------------------------------------------------//
void LinuxForceFeedback::_updateConditionalEffect(const Effect* eff)
{
	struct ff_effect event;

	ConditionalEffect* effect = static_cast<ConditionalEffect*>(eff->getForceEffect());

	_setCommonProperties(&event, NULL, eff, NULL);

	switch(eff->type)
	{
		case OIS::Effect::Friction:
			event.type = FF_FRICTION;
			break;
		case OIS::Effect::Damper:
			event.type = FF_DAMPER;
			break;
		case OIS::Effect::Inertia:
			event.type = FF_INERTIA;
			break;
		case OIS::Effect::Spring:
			event.type = FF_SPRING;
			break;
		default:
			OIS_EXCEPT(E_General, "No such available effect for Conditional force!");
			break;
	}

	event.id = -1;

	event.u.condition[0].right_saturation = LinuxSignedLevel(effect->rightSaturation);
	event.u.condition[0].left_saturation  = LinuxSignedLevel(effect->leftSaturation);
	event.u.condition[0].right_coeff	  = LinuxSignedLevel(effect->rightCoeff);
	event.u.condition[0].left_coeff		  = LinuxSignedLevel(effect->leftCoeff);
	event.u.condition[0].deadband		  = LinuxPositiveLevel(effect->deadband); // Unit ??
	event.u.condition[0].center			  = LinuxSignedLevel(effect->center);	  // Unit ?? TODO ?

	// TODO support for second condition
	event.u.condition[1] = event.u.condition[0];

#if(OIS_LINUX_JOYFF_DEBUG > 1)
	cout << "  Condition[0] : " << endl
		 << "    RightSaturation  : " << effect->rightSaturation
		 << " => " << event.u.condition[0].right_saturation << endl
		 << "    LeftSaturation   : " << effect->leftSaturation
		 << " => " << event.u.condition[0].left_saturation << endl
		 << "    RightCoefficient : " << effect->rightCoeff
		 << " => " << event.u.condition[0].right_coeff << endl
		 << "    LeftCoefficient : " << effect->leftCoeff
		 << " => " << event.u.condition[0].left_coeff << endl
		 << "    DeadBand        : " << effect->deadband
		 << " => " << event.u.condition[0].deadband << endl
		 << "    Center          : " << effect->center
		 << " => " << event.u.condition[0].center << endl;
	cout << "  Condition[1] : Not implemented" << endl;
#endif
	_upload(&event, eff);
}

//--------------------------------------------------------------//
void LinuxForceFeedback::_upload(struct ff_effect* ffeffect, const Effect* effect)
{
	struct ff_effect* linEffect = 0;

	//Get the effect - if it exists
	EffectList::iterator i = mEffectList.find(effect->_handle);
	//It has been created already
	if(i != mEffectList.end())
		linEffect = i->second;

	if(linEffect == 0)
	{
#if(OIS_LINUX_JOYFF_DEBUG > 1)
		cout << endl
			 << "LinuxForceFeedback(" << mJoyStick << ") : Adding new effect : "
			 << Effect::getEffectTypeName(effect->type) << endl;
#endif

		//This effect has not yet been created, so create it in the device
		if(ioctl(mJoyStick, EVIOCSFF, ffeffect) == -1)
		{
			// TODO device full check
			// OIS_EXCEPT(E_DeviceFull, "Remove an effect before adding more!");
			OIS_EXCEPT(E_General, "Unknown error creating effect (may be the device is full)->..");
		}

		// Save returned effect handle
		effect->_handle = ffeffect->id;

		// Save a copy of the uploaded effect for later simple modifications
		linEffect = (struct ff_effect*)calloc(1, sizeof(struct ff_effect));
		memcpy(linEffect, ffeffect, sizeof(struct ff_effect));

		mEffectList[effect->_handle] = linEffect;

		// Start playing the effect.
		_start(effect->_handle);
	}
	else
	{
#if(OIS_LINUX_JOYFF_DEBUG > 1)
		cout << endl
			 << "LinuxForceFeedback(" << mJoyStick << ") : Replacing effect : "
			 << Effect::getEffectTypeName(effect->type) << endl;
#endif

		// Keep same id/handle, as this is just an update in the device.
		ffeffect->id = effect->_handle;

		// Update effect in the device.
		if(ioctl(mJoyStick, EVIOCSFF, ffeffect) == -1)
		{
			OIS_EXCEPT(E_General, "Unknown error updating an effect->..");
		}

		// Update local linEffect for next time.
		memcpy(linEffect, ffeffect, sizeof(struct ff_effect));
	}

#if(OIS_LINUX_JOYFF_DEBUG > 1)
	cout << "LinuxForceFeedback(" << mJoyStick
		 << ") : Effect handle : " << effect->_handle << endl;
#endif
}

//--------------------------------------------------------------//
void LinuxForceFeedback::_stop(int handle)
{
	struct input_event stop;

	stop.type  = EV_FF;
	stop.code  = handle;
	stop.value = 0;

#if(OIS_LINUX_JOYFF_DEBUG > 1)
	cout << endl
		 << "LinuxForceFeedback(" << mJoyStick
		 << ") : Stopping effect with handle " << handle << endl;
#endif

	if(write(mJoyStick, &stop, sizeof(stop)) != sizeof(stop))
	{
		OIS_EXCEPT(E_General, "Unknown error stopping effect->..");
	}
}

//--------------------------------------------------------------//
void LinuxForceFeedback::_start(int handle)
{
	struct input_event play;

	play.type  = EV_FF;
	play.code  = handle;
	play.value = 1; // Play once.

#if(OIS_LINUX_JOYFF_DEBUG > 1)
	cout << endl
		 << "LinuxForceFeedback(" << mJoyStick
		 << ") : Starting effect with handle " << handle << endl;
#endif

	if(write(mJoyStick, &play, sizeof(play)) != sizeof(play))
	{
		OIS_EXCEPT(E_General, "Unknown error playing effect->..");
	}
}

//--------------------------------------------------------------//
void LinuxForceFeedback::_unload(int handle)
{
#if(OIS_LINUX_JOYFF_DEBUG > 1)
	cout << endl
		 << "LinuxForceFeedback(" << mJoyStick
		 << ") : Removing effect with handle " << handle << endl;
#endif

	if(ioctl(mJoyStick, EVIOCRMFF, handle) == -1)
	{
		OIS_EXCEPT(E_General, "Unknown error removing effect->..");
	}
}
