/**
 ******************************************************************************
 * @addtogroup OpenPilotModules OpenPilot Modules
 * @{ 
 * @addtogroup ManualControlModule Manual Control Module
 * @brief Provide manual control or allow it alter flight mode.
 * @{
 *
 * Reads in the ManualControlCommand FlightMode setting from receiver then either 
 * pass the settings straght to ActuatorDesired object (manual mode) or to
 * AttitudeDesired object (stabilized mode)
 *
 * @file       manualcontrol.c
 * @author     The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
 * @brief      ManualControl module. Handles safety R/C link and flight mode.
 *
 * @see        The GNU Public License (GPL) Version 3
 *
 *****************************************************************************/
/*
 * 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
 */

#include "openpilot.h"
#include "manualcontrol.h"
#include "manualcontrolsettings.h"
#include "stabilizationsettings.h"
#include "manualcontrolcommand.h"
#include "actuatordesired.h"
#include "attitudedesired.h"
#include "attitudesettings.h"


// Private constants
#define STACK_SIZE configMINIMAL_STACK_SIZE
#define TASK_PRIORITY (tskIDLE_PRIORITY+4)
#define UPDATE_PERIOD_MS 20
#define THROTTLE_FAILSAFE -0.1
#define FLIGHT_MODE_LIMIT 1.0/3.0

// Private types

// Private variables
static xTaskHandle taskHandle;

// Private functions
static void manualControlTask(void* parameters);
static float scaleChannel(int16_t value, int16_t max, int16_t min, int16_t neutral);

/**
 * Module initialization
 */
int32_t ManualControlInitialize()
{
	// Start main task
	xTaskCreate(manualControlTask, (signed char*)"ManualControl", STACK_SIZE, NULL, TASK_PRIORITY, &taskHandle);

	return 0;
}

/**
 * Module task
 */
static void manualControlTask(void* parameters)
{
	ManualControlSettingsData settings;
	StabilizationSettingsData stabSettings;
	ManualControlCommandData cmd;
	ActuatorDesiredData actuator;
	AttitudeDesiredData attitude;
	portTickType lastSysTime;
	float flightMode;
	
	uint8_t disconnected_count = 0;	
	uint8_t connected_count = 0;	
	enum {CONNECTED, DISCONNECTED} connection_state = DISCONNECTED;

	// Main task loop
	lastSysTime = xTaskGetTickCount();
	while (1)
	{
		// Wait until next update
		vTaskDelayUntil(&lastSysTime, UPDATE_PERIOD_MS / portTICK_RATE_MS );
    
		// Read settings
		ManualControlSettingsGet(&settings);
		StabilizationSettingsGet(&stabSettings);

        if(!ManualControlCommandReadOnly(&cmd))
        {
            // Check settings, if error raise alarm
            if ( settings.Roll >= MANUALCONTROLSETTINGS_ROLL_NONE ||
                settings.Pitch >= MANUALCONTROLSETTINGS_PITCH_NONE ||
                settings.Yaw >= MANUALCONTROLSETTINGS_YAW_NONE ||
                settings.Throttle >= MANUALCONTROLSETTINGS_THROTTLE_NONE ||
                settings.FlightMode >= MANUALCONTROLSETTINGS_FLIGHTMODE_NONE )
            {
                AlarmsSet(SYSTEMALARMS_ALARM_MANUALCONTROL, SYSTEMALARMS_ALARM_CRITICAL);
                cmd.FlightMode = MANUALCONTROLCOMMAND_FLIGHTMODE_AUTO;
                cmd.Connected = MANUALCONTROLCOMMAND_CONNECTED_FALSE;
                ManualControlCommandSet(&cmd);
                continue;
            }
            
            // Read channel values in us
            // TODO: settings.InputMode is currently ignored because PIOS will not allow runtime
            // selection of PWM and PPM. The configuration is currently done at compile time in
            // the pios_config.h file.
            for (int n = 0; n < MANUALCONTROLCOMMAND_CHANNEL_NUMELEM; ++n)
            {
#if defined(PIOS_INCLUDE_PWM)
                cmd.Channel[n] = PIOS_PWM_Get(n);
#elif defined(PIOS_INCLUDE_PPM)
                cmd.Channel[n] = PIOS_PPM_Get(n);
#elif defined(PIOS_INCLUDE_SPEKTRUM)
                cmd.Channel[n] = PIOS_SPEKTRUM_Get(n);
#endif
            }
            
            // Calculate roll command in range +1 to -1
            cmd.Roll = scaleChannel( cmd.Channel[settings.Roll], settings.ChannelMax[settings.Roll],
                                    settings.ChannelMin[settings.Roll], settings.ChannelNeutral[settings.Roll] );
            
            // Calculate pitch command in range +1 to -1
            cmd.Pitch = scaleChannel( cmd.Channel[settings.Pitch], settings.ChannelMax[settings.Pitch],
                                     settings.ChannelMin[settings.Pitch], settings.ChannelNeutral[settings.Pitch] );
            
            // Calculate yaw command in range +1 to -1
            cmd.Yaw = scaleChannel( cmd.Channel[settings.Yaw], settings.ChannelMax[settings.Yaw],
                                   settings.ChannelMin[settings.Yaw], settings.ChannelNeutral[settings.Yaw] );
            
            // Calculate throttle command in range +1 to -1
            cmd.Throttle = scaleChannel( cmd.Channel[settings.Throttle], settings.ChannelMax[settings.Throttle],
                                        settings.ChannelMin[settings.Throttle], settings.ChannelNeutral[settings.Throttle] );
			
			if(settings.Accessory1 != MANUALCONTROLSETTINGS_ACCESSORY1_NONE)
				cmd.Accessory1 = scaleChannel( cmd.Channel[settings.Accessory1], settings.ChannelMax[settings.Accessory1],
											  settings.ChannelMin[settings.Accessory1], settings.ChannelNeutral[settings.Accessory1] );
			else 
				cmd.Accessory1 = 0;
			
			if(settings.Accessory2 != MANUALCONTROLSETTINGS_ACCESSORY2_NONE)
				cmd.Accessory2 = scaleChannel( cmd.Channel[settings.Accessory2], settings.ChannelMax[settings.Accessory2],
											  settings.ChannelMin[settings.Accessory2], settings.ChannelNeutral[settings.Accessory2] );
			else 
				cmd.Accessory2 = 0;

			if(settings.Accessory3 != MANUALCONTROLSETTINGS_ACCESSORY3_NONE)
				cmd.Accessory3 = scaleChannel( cmd.Channel[settings.Accessory3], settings.ChannelMax[settings.Accessory3],
											  settings.ChannelMin[settings.Accessory3], settings.ChannelNeutral[settings.Accessory3] );
			else 
				cmd.Accessory3 = 0;
			
            
            // Update flight mode
            flightMode = scaleChannel( cmd.Channel[settings.FlightMode], settings.ChannelMax[settings.FlightMode],
                                      settings.ChannelMin[settings.FlightMode], settings.ChannelNeutral[settings.FlightMode] );
            if (flightMode < -FLIGHT_MODE_LIMIT)
            {
                cmd.FlightMode = MANUALCONTROLCOMMAND_FLIGHTMODE_MANUAL;
            }
            else if (flightMode > FLIGHT_MODE_LIMIT)
            {
                cmd.FlightMode = MANUALCONTROLCOMMAND_FLIGHTMODE_AUTO;
            }
            else
            {
                cmd.FlightMode = MANUALCONTROLCOMMAND_FLIGHTMODE_STABILIZED;
            }
            // Update the ManualControlCommand object
            ManualControlCommandSet(&cmd);
            // This seems silly to set then get, but the reason is if the GCS is 
            // the control input, the set command will be blocked by the read only
            // setting and the get command will pull the right values from telemetry
        }
        else
            ManualControlCommandGet(&cmd); /* Under GCS control */

		// Implement hysteresis loop on connection status
		// Must check both Max and Min in case they reversed
		if (!ManualControlCommandReadOnly(&cmd) &&
			cmd.Channel[settings.Throttle] < settings.ChannelMax[settings.Throttle]  &&
			cmd.Channel[settings.Throttle] < settings.ChannelMin[settings.Throttle]) 
		{
			if (disconnected_count++ > 10)
			{
				connection_state = DISCONNECTED;
				connected_count = 0;
				disconnected_count = 0;
			} else
				disconnected_count++;
		} else {
			if (connected_count++ > 10)
			{
				connection_state = CONNECTED;
				connected_count = 0;
				disconnected_count = 0;
			}
			else 
				connected_count++;
		}
		
		if (connection_state == DISCONNECTED) {
			cmd.Connected = MANUALCONTROLCOMMAND_CONNECTED_FALSE;
			cmd.Throttle = -1; // Shut down engine with no control
			cmd.Roll = 0;
			cmd.Yaw = 0;
			cmd.Pitch = 0;
			//cmd.FlightMode = MANUALCONTROLCOMMAND_FLIGHTMODE_AUTO; // don't do until AUTO implemented and functioning
			AlarmsSet(SYSTEMALARMS_ALARM_MANUALCONTROL, SYSTEMALARMS_ALARM_WARNING);
			ManualControlCommandSet(&cmd);
		}
		else
		{
			cmd.Connected = MANUALCONTROLCOMMAND_CONNECTED_TRUE;
			AlarmsClear(SYSTEMALARMS_ALARM_MANUALCONTROL);
			ManualControlCommandSet(&cmd);
		}
    
		// Depending on the mode update the Stabilization or Actuator objects
		if ( cmd.FlightMode == MANUALCONTROLCOMMAND_FLIGHTMODE_MANUAL )
		{
			actuator.Roll = cmd.Roll;
			actuator.Pitch = cmd.Pitch;
			actuator.Yaw = cmd.Yaw;
			actuator.Throttle = cmd.Throttle;
			ActuatorDesiredSet(&actuator);
		}
		else if ( cmd.FlightMode == MANUALCONTROLCOMMAND_FLIGHTMODE_STABILIZED )
		{
			attitude.Roll = cmd.Roll*stabSettings.RollMax;
			attitude.Pitch = cmd.Pitch*stabSettings.PitchMax;
			if (cmd.Yaw<0)
			{
				attitude.Yaw = 360 + (cmd.Yaw*180.0);
			}
			else
			{
				attitude.Yaw = (cmd.Yaw*180.0);
			}
			if(cmd.Throttle < 0)
				attitude.Throttle = -1;
			else
				attitude.Throttle = cmd.Throttle*stabSettings.ThrottleMax;
			AttitudeDesiredSet(&attitude);
		}
		
		if( cmd.Accessory3 < -.5 ) { //TODO: Make what happens here depend on GCS
			AttitudeSettingsData attitudeSettings;
			AttitudeSettingsGet(&attitudeSettings);
			// Hard coding a maximum bias of 15 for now... maybe mistake
			attitudeSettings.PitchBias = cmd.Accessory1 * 15;
			attitudeSettings.RollBias = cmd.Accessory2 * 15;
			AttitudeSettingsSet(&attitudeSettings);
		} else if (cmd.Accessory3 > .9) { 
			// REALLY don't want to end up here accidentally.  I've also saved by meta for Stabilization setting to be
			// flight read only by default
			StabilizationSettingsData stabSettings;
			StabilizationSettingsGet(&stabSettings);			
			if(cmd.Accessory1 > 0)
				stabSettings.PitchKp = cmd.Accessory1 * 0.05; 
			if(cmd.Accessory2 > 0)
				stabSettings.RollKp = cmd.Accessory2 * 0.05; 
			StabilizationSettingsSet(&stabSettings);
		}
	}
}

/**
 * Convert channel from servo pulse duration (microseconds) to scaled -1/+1 range.
 */
static float scaleChannel(int16_t value, int16_t max, int16_t min, int16_t neutral)
{
	float valueScaled;
	// Scale
	if ( (max > min && value >= neutral) || (min > max && value <= neutral) )
	{
		if ( max != neutral )
		{
			valueScaled = (float)(value-neutral)/(float)(max-neutral);
		}
		else
		{
			valueScaled = 0;
		}
	}
	else
	{
		if ( min != neutral )
		{
			valueScaled = (float)(value-neutral)/(float)(neutral-min);
		}
		else
		{
			valueScaled = 0;
		}
	}
	// Bound
	if ( valueScaled > 1.0 )
	{
		valueScaled = 1.0;
	}
	else if ( valueScaled < -1.0 )
	{
		valueScaled = -1.0;
	}
	return valueScaled;
}

/**
  * @}
  * @}
  */
