/*******************************************************************************
  Copyright (C) 2013 Jasem Mutlaq <mutlaqja@ikarustech.com>

 This library is free software; you can redistribute it and/or
 modify it under the terms of the GNU Library General Public
 License version 2 as published by the Free Software Foundation.

 This library 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
 Library General Public License for more details.

 You should have received a copy of the GNU Library General Public License
 along with this library; see the file COPYING.LIB.  If not, write to
 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 Boston, MA 02110-1301, USA.
*******************************************************************************/

#pragma once

#include "defaultdevice.h"

#include <ciso646> // detect std::lib
#include <functional>

namespace INDI
{
/**
 * \class INDI::Controller
 * @brief The Controller class provides functionality to access a controller (e.g. joystick) input and send it to the requesting driver.
 *
 * - To use the class in an INDI::DefaultDevice based driver:
 *
 *    -# Set the callback functions for each of input type requested.
 *
 *    -# Map the properties you wish to /e listen to from the joystick driver by specifying
 *       the type of input requested and the initial default value.
 *       For example:
 *       \code{.cpp}
 *       mapController("ABORT", "Abort Telescope", INDI::Controller::CONTROLLER_BUTTON, "BUTTON_1");
 *       \endcode
 *       After mapping all the desired controls, call the class initProperties() function.
 *       If the user enables joystick support in the driver and presses a button on the joystick, the button
 *       callback function will be invoked with the name & state of the button.
 *
 *    -# Call the Controller's ISGetProperties(), initProperties(), updateProperties(), saveConfigItems(), and
 *       ISNewXXX functions from the same standard functions in your driver.
 *
 * The class communicates with INDI joystick driver which in turn enumerates the game pad and provides
 * three types of constructs:
 * <ul>
 * <li><b>Joysticks</b>: Each joystick displays a normalized magnitude [0 to 1] and an angle. The angle is measured counter clock wise starting from
 *  the right/east direction [0 to 360]. They are defined as JOYSTICK_# where # is the joystick number.</li>
 * <li><b>Axes</b>: Each joystick has two or more axes. Each axis has a raw value and angle. The raw value ranges from -32767.0 to 32767.0 They are
 * defined as AXIS_# where # is the axis number.</li>
 * <li><b>Buttons</b>: Buttons are either on or off. They are defined as BUTTON_# where # is the button number.</li>
 * </ul>
 *
 * \note All indexes start from 1. i.e. There is no BUTTON_0 or JOYSTICK_0.
 * \see See the LX200 Generic & Celestron GPS drivers for an example implementation.
 * \warning Both the indi_joystick driver and the driver using this class must be running in the same INDI server
 *         (or chained INDI servers) in order for it to work as it depends on snooping among drivers.
 * \author Jasem Multaq
 */
class Controller
{
    public:
        typedef enum { CONTROLLER_JOYSTICK, CONTROLLER_AXIS, CONTROLLER_BUTTON, CONTROLLER_UNKNOWN } ControllerType;

        /**
             * @brief joystickFunc Joystick callback function signature.
             */
        typedef std::function<void(const char *joystick_n, double mag, double angle, void *context)> joystickFunc;

        /**
             * @brief axisFunc Axis callback function signature.
             */
        typedef std::function<void(const char *axis_n, double value, void *context)> axisFunc;

        /**
             * @brief buttonFunc Button callback function signature.
             */
        typedef std::function<void(const char *button_n, ISState state, void *context)> buttonFunc;

        /**
             * @brief Controller Default ctor
             * @param cdevice INDI::DefaultDevice device
             */
        Controller(INDI::DefaultDevice *cdevice);
        virtual ~Controller();

        virtual void ISGetProperties(const char *dev);
        virtual bool initProperties();
        virtual bool updateProperties();
        virtual bool ISSnoopDevice(XMLEle *root);
        virtual bool ISNewSwitch(const char *dev, const char *name, ISState *states, char *names[], int n);
        virtual bool ISNewText(const char *dev, const char *name, char *texts[], char *names[], int n);
        virtual bool saveConfigItems(FILE *fp);

        /**
             * @brief mapController adds a new property to the joystick's settings.
             * @param propertyName Name
             * @param propertyLabel Label
             * @param type The input type of the property. This value cannot be updated.
             * @param initialValue Initial value for the property.
             */
        void mapController(const char *propertyName, const char *propertyLabel, ControllerType type,
                           const char *initialValue);

        /**
             * @brief clearMap clears all properties added previously by mapController()
             */
        void clearMap();

        /**
             * @brief setJoystickCallback Sets the callback function when a new joystick input is detected.
             * @param joystickCallback the callback function.
             */
        void setJoystickCallback(joystickFunc joystickCallback);

        /**
             * @brief setAxisCallback Sets the callback function when a new axis input is detected.
             * @param axisCallback the callback function.
             */
        void setAxisCallback(axisFunc axisCallback);

        /**
             * @brief setButtonCallback Sets the callback function when a new button input is detected.
             * @param buttonCallback the callback function.
             */
        void setButtonCallback(buttonFunc buttonCallback);

        ControllerType getControllerType(const char *name);
        const char *getControllerSetting(const char *name);

    protected:
        static void joystickEvent(const char *joystick_n, double mag, double angle, void *context);
        static void axisEvent(const char *axis_n, int value, void *context);
        static void buttonEvent(const char *button_n, int value, void *context);

        void enableJoystick();
        void disableJoystick();

        joystickFunc joystickCallbackFunc;
        buttonFunc buttonCallbackFunc;
        axisFunc axisCallbackFunc;

        INDI::DefaultDevice *device;

    private:
        /* Joystick Support */
        ISwitchVectorProperty UseJoystickSP;
        ISwitch UseJoystickS[2];

        ITextVectorProperty JoystickDeviceTP;
        IText JoystickDeviceT[1] {};

        ITextVectorProperty JoystickSettingTP;
        IText *JoystickSettingT = nullptr;
};
}
