/*! @file
 *  @brief This file keeps the MoCon device. It supports N motors.
 *
 * @author Florian Briegel
 * $Header: https://svn.mpia.de/gulli/TwiceAsNice/branch/unstable/TwiceAsNice/basda-mocca/src/device/pi/BasdaMoccaPiNDevice.h 14129 2017-01-12 15:16:08Z briegel $
 * @version 2012.01.25
 * @date 2012.01.25
 */

#pragma once

#include <Basda/Mocca/NDevice.h>
#include <Nice/Map/NPoint.h>
#include <Nice/Chat/Line.h>

namespace Basda
{

    struct MoccaException : Nice::Exception
    {
    };

    namespace Pi
    {
        struct Exception : MoccaException
        {
        };

        struct NotImplementedException : Exception
        {
        };

        struct TimeOutException : Exception
        {
        };

        struct ConnectionRefusedException : Exception
        {
        };

        struct ConnectionFormatException : Exception
        {
        };
    }
    ;

    /**
     * @brief This is the Mocca Pi device, with support for N motors.
     *
     *
     * @author Florian Briegel
     * @version 2012.01.25
     * @date 2012.01.25
     *
     */
    class MoccaPiNDevice : public Basda::MoccaNDevice
    {
        public:
            /**
             * @brief Constructor
             *
             * The constructor gets the root node of the device as parameter
             * like SVC.DEV.THE_DEVICE and creates a user defined motor controller
             * device. Thereby a motor object is generated for each founded module
             * in the property tree. The generated motor object reserves the motor
             * to avoid a multiple access from other clients. The corresponding
             * card object will also be generated and stored in a list.
             *
             * @param _config the root node of the device which contain all modules
             *
             */
            MoccaPiNDevice(const Nice::JointPtr &_config);

            /**
             * @brief Destructor
             *
             * removes all created bridges
             */
            virtual ~MoccaPiNDevice();

            /**
             * @brief This method might be called when the service goes in the
             *        INIT state. It connects to all motor controller.
             *
             */
            virtual void open();

            /**
             * @brief This method might be called when the service goes in the
             *        OFFLINE state.
             *
             * The connection to the modules will be closed.
             *
             * @todo implementation of the OFFLINE state
             */
            virtual void close();

            /**
             * @brief This method might be called when the service will be reseted.
             *
             * The connection to the modules will be closed and opened again.
             *
             * @param _asynch the call back object
             *
             * @todo implementation to reset the service
             */
            virtual void reset(DeviceAsync &_asynch = s_async);

            /**
             * @brief This method returns the version string of the device.
             *
             * @todo implementation of the version string
             */
            virtual void version();

            /**
             * @brief This method initialize the device and set the state to INIT.
             *
             * @param _asynch the call back object
             *
             * @todo implementation of the INIT state
             */
            virtual void init(DeviceAsync & = s_async);

            /**
             * @brief This method deinitialize the device and set the state to OFFLINE.
             *
             * @param _asynch the call back object
             *
             * @todo implementation of the OFFLINE state
             */
            virtual void deinit(DeviceAsync & = s_async);

            /**
             * @brief activate something
             *
             * @param _asynch the call back object
             *
             * @todo implementation of the service activation
             */
            virtual void activate(DeviceAsync & = s_async);

            /**
             * @brief deactivate something
             *
             * @param _asynch the call back object
             *
             * @todo implementation of the service deactivation
             */
            virtual void deactivate(DeviceAsync & = s_async);

            /**
             * @brief This method send the command string to the electronic.
             *
             * This method is set to public due to requests from Lorenzo Busoni.
             * BUT THE USER OF THIS METHOD TAKE OVER THE RESPONSIBILITY OF THE
             * CORRECT ERROR HANDLING.
             * THE DRIVER DOES NOT HAVE THE CONTROL OVER THAT DEVICE ANY MORE.
             *
             * @param _commandID  the command ID
             * @param _cardNo     the ID of the MPIA electronic card
             * @param _moduleNo   the ID of the device module
             * @param _selectID   the sub command ID
             * @param _parameter  the parameter as string
             * @param _numberOfLines the number of lines that might be read
             *
             * Hi Martin,
             *
             * that's exactly what I was thinking:
             * The use case is (real example from our experience):
             * we discover at the telescope that we need to continuously monitor a
             * given property (PropA) of the MoCon (obviously because of a
             * malfunctioning in the hardware!). Without this monitor, we will be
             * stuck. You can add the required functionality in the Mocca adding the
             * function getPropA: this requires 4 hours and the day is lost. The day
             * after you are stuck because you discover that you miss PropB, and the
             * day is lost again.
             * As an alternative, one can add the 2 lines below in the code (with a
             * comment: "1 10 0 = getPropA") and this requires 5 minutes. The day
             * after one can add  a similar line with 1 11 0. When you're back in
             * Europe you implement getPropA and getPropB in Mocca and you cleanup
             * the code removing the receiveResponseForComment
             *
             * Generally speaking I think it is a safe idea to have a generic
             * interface to access _EVERY_ possible functionality of a device,
             * because soon or later you'll need it. In the case of the Mocon it is
             * quite easy, a good compromise between living dangerously and wasting
             * too much time in the effort of a complete implementation of all the
             * MoCon commands.
             *
             * Lorenzo Busoni
             */
            virtual void chat(std::string _commandID,
                    std::string _cardNo,
                    std::string _moduleNo,
                    std::string _selectID,
                    std::string _parameter,
                    std::string _numberOfLines,
                    DeviceAsync & = s_async);

            /**
             * @brief This method was requested by Lorenzo Busoni (lbusoni@arcetri.astro.it 02.03.2012)
             *
             * It supports a direct communication with the MoCon.
             * BUT THE USER OF THIS METHOD TAKE OVER THE RESPONSIBILITY OF THE
             * CORRECT ERROR HANDLING.
             * THE DRIVER DOES NOT HAVE THE CONTROL OVER THAT DEVICE ANY MORE.
             *
             * @param _ret the allocated memory for the command response
             *
             * @see chat
             */

            virtual std::string dontParse(DeviceAsync & = s_async);

            /**
             * @brief This method was requested by Lorenzo Busoni (lbusoni@arcetri.astro.it 02.03.2012)
             *
             * It supports a direct communication with the MoCon without to parse
             * the response.
             * BUT THE USER OF THIS METHOD TAKE OVER THE RESPONSIBILITY OF THE
             * CORRECT ERROR HANDLING.
             * THE DRIVER DOES NOT HAVE THE CONTROL OVER THAT DEVICE ANY MORE.
             *
             * @param _commandID   the command ID which is defined in the CommandIDs.h
             * @param _cardNo      the card number
             * @param _moduleNo    the module number
             * @param _ret         the allocated space for the returned data
             * @param _numberOfLines  optional: This parameter imitates how many
             *                        data should be read from the Ethernet buffer.
             *                        All data are read if the value is -1.
             *
             * @return the return parameter is stored in _ret parameter.
             *
             * @see chat
             */
            virtual std::string receiveDataRaw(std::string _commandID,
                    std::string _cardNo,
                    std::string _moduleNo,
                    std::string _numberOfLines,
                    DeviceAsync & = s_async);

            virtual bool isReachable();

            virtual void prepareMoveSync(Nice::BitSet _motors);
            virtual void startMoveSync();
            /**
             * @brief This method setup the acceleration  anv velocity for a
             *        direct point to point move.
             *
             * @param _acceleration
             * @param _velocity
             * @param _deceleration
             */
            void prepareDirectMove(const Nice::NPoint &_acceleration,
                                   const Nice::NPoint &_velocity,
                                   const Nice::NPoint &_deceleration);

            /**
             * @brief All motors of the device are looking for the reference
             *        positions and reset the step counter to zero.
             *
             * @param _asynch the call back object
             */
            virtual void moveToHome(Basda::DeviceAsync & =
                    Basda::Device::s_async);
            virtual void moveToHomeStart(Basda::DeviceAsync & =
                    Basda::Device::s_async);
            virtual void moveToHomeWait(Basda::DeviceAsync & =
                    Basda::Device::s_async);

            virtual void moveToHomeSingle(int _motor, DeviceAsync & = s_async);
            virtual void moveToHomeSingleStart(int _motor, DeviceAsync & = s_async);
            virtual void moveToHomeSingleWait(DeviceAsync & = s_async);

            /**
             * @brief All motors of the device move to the positive software
             *        limit switch.
             *
             * The idea of this method is that the motors move to the positive
             * software limit switch.
             *
             * @param _asynch the call back object
             *
             * @todo implementation
             */
            virtual void moveToPositiveLimit(Basda::DeviceAsync & =
                    Basda::Device::s_async);
            virtual void moveToPositiveLimitStart(Basda::DeviceAsync & =
                    Basda::Device::s_async);
            virtual void moveToPositiveLimitWait(Basda::DeviceAsync & =
                    Basda::Device::s_async);

            /**
             * @brief All motors of the device move to the negative software
             *        limit switch.
             *
             * The idea of this method is that the motors move to the negative
             * software limit switch.
             *
             * @param _asynch the call back object
             *
             * @todo implementation
             */
            virtual void moveToNegativeLimit(Basda::DeviceAsync & =
                    Basda::Device::s_async);
            virtual void moveToNegativeLimitStart(Basda::DeviceAsync & =
                    Basda::Device::s_async);
            virtual void moveToNegativeLimitWait(Basda::DeviceAsync & =
                    Basda::Device::s_async);

            /**
             * @brief move all motors to the positive limit switches (_limits[i]>0)
             *        or negative (_limits[i]<0) limit switches.
             *
             * @param _limit a vector that contains to which limit the motors
             *               have to move (_limits[i]>0 - to positive limit)
             *               (_limits[i]<0 - to negative limit) whereas the order
             *               of the points is corresponding to the order of the
             *               motor names in the property tree (alphabetical).
             * @param _asynch the call back object
             *
             * The current limit switch state of all motors.
             */
            virtual void moveToLimit(const Nice::NPoint &_limit,
                    Basda::DeviceAsync & = Basda::Device::s_async);
            virtual void moveToLimitStart(const Nice::NPoint &_limit,
                    Basda::DeviceAsync & = Basda::Device::s_async);
            virtual void moveToLimitWait(Basda::DeviceAsync & = Basda::Device::s_async);

            /**
             * @brief The motors that are stored in the vector, move to the
             *        absolute position.
             *
             * @param _positions are the target positions of type Nice::NPoint
             *                   whereas the order of the points is corresponding
             *                   to the order of the motor names in the property
             *                   tree (alphabetical).
             * @param _asynch the call back object
             */
            virtual void moveAbsolute(const Nice::NPoint &_positions,
                    Basda::DeviceAsync & = Basda::Device::s_async);
            virtual void moveAbsoluteStart(const Nice::NPoint &_positions,
                    Basda::DeviceAsync & = Basda::Device::s_async);
            virtual void moveAbsoluteWait(Basda::DeviceAsync & = Basda::Device::s_async);

            /**
             * @brief The motors that are stored in the vector move to the
             *        relative position.
             *
             * @param _positions are the target positions of type Nice::NPoint
             *                   whereas the order of the points is corresponding
             *                   to the order of the motor names in the property
             *                   tree (alphabetical).
             * @param _asynch the call back object
             */
            virtual void moveRelative(const Nice::NPoint &_positions,
                    Basda::DeviceAsync & = Basda::Device::s_async);
            virtual void moveRelativeStart(const Nice::NPoint &_positions,
                    Basda::DeviceAsync & = Basda::Device::s_async);
            virtual void moveRelativeWait(Basda::DeviceAsync & = Basda::Device::s_async);

            /**
             * @brief This method stops the motor with a deceleration.
             */
            virtual void stop();

            /**
             * @brief Abort the moving of the motor immediately without deceleration.
             *
             * @note Abrupt stop of a fast running motor can leads to losing steps.
             *       The motor could be damaged too.
             */
            virtual void abort();

            /**
             * @brief Returns the current positions of all motors.
             *
             * The current position of all motors will be stored in the property
             *
             * - VAR.POSITION
             */
            virtual Nice::NPoint getPosition();
            virtual Nice::NPoint getDeviceEncoderPosition();
            virtual Nice::NPoint getAbsoluteEncoderPosition();
            virtual Nice::NPoint getIncrementalEncoderPosition();

            virtual void setPosition(const Nice::NPoint &_position);

            /**
             * @brief Returns the current velocity of all motors.
             *
             * The current velocity of all motors will be stored in the property
             *
             * - VAR.VELOCITY
             */
            virtual Nice::NPoint getVelocity();

            /**
             * @brief Returns the basic settings of all motors.
             *
             * Depending of the implementation the basic settings could be:
             * motion profile, revolution, velocity, acceleration, deceleration
             * and jerk. Each basic settings is stored in a sequence.
             *
             * @return a sequence of all motors with a sequence of all basic settings
             */
            virtual Nice::SeqSeqDouble getBasicSettings();

            /**
             * @brief Set the velocity configuration parameter of all motors.
             *
             * @param _velocity a vector with the velocity of all motors. The
             *        order of the velocities is corresponding to the order of
             *        the motor names in the property tree (alphabetical).
             */
            virtual void setVelocitySetting(const Nice::NPoint &_velocity);

            /**
             * @brief Set the acceleration configuration parameter of all motors.
             *
             * @param _acceleration a vector with the acceleration of all motors. The
             *        order of the accelerations is corresponding to the order of
             *        the motor names in the property tree (alphabetical).
             */
            virtual void setAccelerationSetting(const Nice::NPoint &_acceleration);

            /**
             * @brief Set the deceleration configuration parameter of all motors.
             *
             * @param _deceleration a vector with the deceleration of all motors. The
             *        order of the decelerations is corresponding to the order of
             *        the motor names in the property tree (alphabetical).
             */
            virtual void setDecelerationSetting(const Nice::NPoint &_deceleration);

            /**
             * @brief Set the jerk configuration parameter of all motors.
             *
             * @param _jerk a vector with the jerk of all motors. The
             *        order of the jerks is corresponding to the order of
             *        the motor names in the property tree (alphabetical).
             */
            virtual void setJerkSetting(const Nice::NPoint &_jerk);

            /**
             * @brief Checks if one of the motors is in motion.
             *
             * The method collects the motion flag of every motor and stores it in
             * the property
             *
             * - VAR.MOVING
             *
             * @param _motion returns which motor is in motion
             * @return true if at least one motor is in motion
             */
            virtual bool isMoving(Nice::BitSet &_motion);

            /**
             * @brief Checks if one of the motors is in limit.
             *
             * This method collects the limit flags of all motors and stores it
             * in the property:
             *
             * - VAR.LIMIT
             *
             * @param _limit - return a vector with all motor limit switch flags
             *                 (0 - not in limit; +/-1 in software limit;
             *                 +/-2 in hardware limit)
             *
             * @return is true when the device is in the limit, false otherwise
             */
            virtual bool isAtLimit(Nice::NPoint &_limit);

            /**
             * @brief This method indicate whether the motor is at the home position.
             *
             * @param _atHome each flag in that Bitset indicate whether the motor
             *                is at home (true - motor is at home;). The order of
             *                the bits is corresponding to the order of the motor
             *                names in the property tree (alphabetical).
             */
            virtual bool isAtHome(Nice::BitSet & _atHome);

            virtual void startExternalProfile(
                    const Nice::Date &_startDate,
                    const Nice::NPoint &_positionSequence,
                    unsigned int _frequency,
                    unsigned int _samplesPerSegment,
                    unsigned int _faultTolerance,
                    Basda::DeviceAsync &_async = Basda::Device::s_async);
            virtual void shinsStartExternalProfile(
                    const Nice::Date &_startDate,
                    const Nice::NPoint &_positionSequence,
                    unsigned int _frequency,
                    unsigned int _samplesPerSegment,
                    unsigned int _faultTolerance,
                    Basda::DeviceAsync &_async = Basda::Device::s_async);

            virtual int shinsGetStartingRamp(const Nice::NPoint & _positionSequence, double time_interval_microseconds){return 0;};
            virtual int sharkDerotatorBacklashProcedure(int procedure){return 0;};

            virtual void changeExternalProfile(
                    const Nice::Date &_date,
                    const Nice::NPoint &_positionSequence);

            virtual void prepareExternalProfile(const Nice::SeqSeqInt &_segmentsX,
                                            const Nice::SeqSeqInt &_segmentsY,
                                            Basda::DeviceAsync &_async);
            virtual void startExternalProfile(Basda::DeviceAsync &_async);
            virtual void shinsStartExternalProfile(Basda::DeviceAsync &_async);

//            /**
//             * @brief The external profile will be configured and the external
//             *        profile will uploaded to the MoCon.
//             *
//             * @param _positionSequence a vector with the profile for each motor
//             * @param _timeResolution segmentLength / samplesPerSegment
//             * @param _samplesPerSegment samples per segment
//             * @param _faultTolerance the maximal error that the external profile
//             *                        can have after the conversion to a MoCon profile
//             * @param _asynch the call back object
//             */
//            virtual void setExternalProfile(const Nice::SeqNPoint & _positionSequence,
//                                            double _timeResolution,
//                                            unsigned int _samplesPerSegment,
//                                            unsigned int _faultTolerance,
//                                            Basda::DeviceAsync &_asynch);
//
//            /**
//             * @brief This method starts the external profile at a certain data.
//             *
//             * The external profile can be stopped with the stop method.
//             *
//             * @param _date a vector with the start date of all motors. The order
//             *              of the dates is corresponding to the order of the motor
//             *              names in the property tree (alphabetical). Use the
//             *              constant Basda::STAY if the motor should move.
//             * @param _asynch the call back object
//             */
//            virtual void startExternalProfile(std::vector<Nice::Date> _date,
//                                              Basda::DeviceAsync &_asynch);
//
//            virtual void pauseExternalProfile();
//
//            virtual void stopExternalProfile();

            virtual void setLinkTimeout(Nice::Time _tcpConnectionTimeout);

            /**
             * @brief This method returns TCP connection timeout.
             *
             * For each motor, that name is stored in the vector, the TCP
             * connection timeout will be stored.
             *
             * @return the TCP connection timeout
             */
            virtual Nice::SeqTime getLinkTimeout();

            /**
             * @brief This method synchronize the MoCon time with the PC time.
             *
             * For each motor, that name is stored in the vector, the current
             * MoCon cycles and the PC time will be stored.
             *
             * @see getCurrentTime()
             * @see Basda::MoConModule::synchronizeTime()
             */
            virtual Nice::NPoint getReferenceTime();

            /**
             * @brief This method returns the current MoCon time.
             *
             * For each motor, that name is stored in the vector, the current
             * MoCon cycles and the PC time will be stored.
             *
             * @see getReferenceTime()
             * @see Basda::MoConModule::getCurrentTime()
             */
            virtual Nice::NPoint getCurrentTime();

            virtual Nice::SeqBitSet getPositionSwitchStatus();

            virtual void dumpStatusInformation(Basda::DeviceAsync &_asynch,
                                               bool _compareParameter = false);

            virtual Nice::Completion getProgress();

            virtual void checkMotionError();

            virtual void setCollision(Nice::SeqPoint _collisions);

        private:
            Nice::ChatLine m_chat;
            Nice::SeqString m_axes;
            Nice::NPoint m_maxPosition;
            Nice::NPoint m_minPosition;
            Nice::NPoint m_homePosition;
            Nice::NPoint m_lastPosition;
            Nice::NPoint m_velocity;
            bool m_isReachable;
            Nice::Timer m_connectionTimer;

            Nice::Mutex m_mutex;

            double m_distance;
            Nice::Timer m_timer;
            Nice::Time m_time;

            Nice::Time m_tcpConnectionTimeout;

            /**
             * @brief Calculate the completion percent.
             *
             * @param _cmd the command name which appears in the completion bar
             * @param _asynch the call back object
             */
            void completion(std::string _cmd,
                    const Nice::NPoint &_targetPositions,
                    Basda::DeviceAsync &_asynch);

            /**
             * @brief Checks if the motion is complete.
             *
             * This method collect the MOTION_COMPLETE flags of all motors.
             *
             * The following property will indicate the motion completeness
             *
             * - VAR.MOTION_COMPLETE
             *
             * @param _motion returns which motor is in motion
             * @return true if all motor have completed its motion
             */
            bool isMotionComplete(Nice::BitSet &_motion);

            void checkPowerdOnFor60Sec();

            void Check4Error(int);
            void read(const char *, Nice::SeqString &);
            template<typename Type> void read(const char *,
                    std::vector<Type> &);
            template<typename Type> void read(const char *,
                    const char *,
                    Type &);

            template<typename Type> void exec(const char *, const Type &);
            template<typename Type> void exec(const char *,
                    const std::vector<Type> &);
//            bool isMoving();
            bool waitOnTarget(double maxdist, Basda::DeviceAsync & _asynch);

            const char * pi_listOfAxisIds()
            {
                return "SAI";
            }
            const char * pi_position()
            {
                return "POS";
            }
            const char * pi_velocity()
            {
                return "VEL";
            }
            const char * pi_servoState()
            {
                return "SVO";
            }
            const char * pi_negLimit()
            {
                return "TMN";
            }
            const char * pi_posLimit()
            {
                return "TMX";
            }
            const char * pi_moveAbs()
            {
                return "MOV";
            }
            const char * pi_moveRel()
            {
                return "MVR";
            }
            const char * pi_onTarget()
            {
                return "ONT";
            }
            const char * pi_stopMovement()
            {
                return "STP";
            }

            const char * cfg_root()
            {
                return "CFG";
            }
            const char * cfg_connectstring()
            {
                return "CFG.CON";
            }

            void setIsReachable(const bool _isReachable);
    };

    /**
     * @brief Definition of the smart pointer of this class.
     */
    typedef IceUtil::Handle<MoccaPiNDevice> MoccaPiNDevicePtr;
}
;

