/*
 *  Hamlib Interface - main file
 *  Copyright (c) 2000-2012 by Stephane Fillod
 *  Copyright (c) 2000-2003 by Frank Singleton
 *
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Lesser General Public
 *   License as published by the Free Software Foundation; either
 *   version 2.1 of the License, or (at your option) any later version.
 *
 *   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
 *   Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public
 *   License along with this library; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */
/* SPDX-License-Identifier: LGPL-2.1-or-later */

/**
 * \addtogroup rotator
 * @{
 */

/**
 * \file src/rotator.c
 * \brief Rotator interface
 * \author Frank Singleton
 * \date 2000-2003
 * \author Stephane Fillod
 * \date 2000-2012
 *
 * This Hamlib interface is a frontend implementing the rotator wrapper
 * functions.
 */


/**
 * \page rot Rotator interface
 *
 * A rotator can be any kind of azimuth, elevation, or azimuth and elevation
 * controlled antenna system or other such aiming equipment, e.g. telescopes,
 * etc.
 */

#include "hamlib/config.h"

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>

#include "hamlib/rotator.h"
#include "hamlib/port.h"
#include "hamlib/rot_state.h"
#include "serial.h"
#include "parallel.h"
#if defined(HAVE_LIB_USB_H) || defined(HAMB_LIBUSB_1_0_LIBUSB_H)
#include "usb_port.h"
#endif
#include "network.h"

#ifndef DOC_HIDDEN

#if defined(WIN32) && !defined(__CYGWIN__)
#  define DEFAULT_SERIAL_PORT "\\\\.\\COM1"
#elif BSD
#  define DEFAULT_SERIAL_PORT "/dev/cuaa0"
#elif MACOSX
#  define DEFAULT_SERIAL_PORT "/dev/cu.usbserial"
#else
#  define DEFAULT_SERIAL_PORT "/dev/ttyS0"
#endif

#if defined(WIN32)
#  define DEFAULT_PARALLEL_PORT "\\\\.\\$VDMLPT1"
#elif defined(HAVE_DEV_PPBUS_PPI_H)
#  define DEFAULT_PARALLEL_PORT "/dev/ppi0"
#else
#  define DEFAULT_PARALLEL_PORT "/dev/parport0"
#endif

#define CHECK_ROT_ARG(r) (!(r) || !(r)->caps || !(ROTSTATE(r)->comm_state))


/*
 * Data structure to track the opened rot (by rot_open)
 */
struct opened_rot_l
{
    ROT *rot;
    struct opened_rot_l *next;
};
static struct opened_rot_l *opened_rot_list = { NULL };


/*
 * track which rot is opened (with rot_open)
 * needed at least for transceive mode
 */
static int add_opened_rot(ROT *rot)
{
    struct opened_rot_l *p;
    p = (struct opened_rot_l *)calloc(1, sizeof(struct opened_rot_l));

    if (!p)
    {
        return -RIG_ENOMEM;
    }

    p->rot = rot;
    p->next = opened_rot_list;
    opened_rot_list = p;
    return RIG_OK;
}


static int remove_opened_rot(const ROT *rot)
{
    struct opened_rot_l *p, *q;
    q = NULL;

    for (p = opened_rot_list; p; p = p->next)
    {
        if (p->rot == rot)
        {
            if (q == NULL)
            {
                opened_rot_list = opened_rot_list->next;
            }
            else
            {
                q->next = p->next;
            }

            free(p);
            return RIG_OK;
        }

        q = p;
    }

    return -RIG_EINVAL; /* Not found in list ! */
}
#endif /* !DOC_HIDDEN */

/** @} */ /* rotator definitions */


/**
 * \addtogroup rot_internal
 * @{
 */


/**
 * \brief Executes \a cfunc on each opened #ROT.
 *
 * \param cfunc The function to be executed on each #ROT.
 * \param data  Data pointer to be passed to \a cfunc.
 *
 * Calls \a cfunc function for each opened #ROT.  The contents of the opened
 * #ROT table is processed in random order according to a function pointed to
 * by \a cfunc, which is called with two arguments, the first pointing to the
 * #ROT handle, the second to a data pointer \a data.
 *
 * If \a data is not needed, then it can be set to NULL.  The processing of
 * the opened #ROT table is stopped when \a cfunc returns 0.
 *
 * \return RIG_OK in all cases.
 */
int foreach_opened_rot(int (*cfunc)(ROT *, rig_ptr_t), rig_ptr_t data)
{
    struct opened_rot_l *p;

    rot_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);

    for (p = opened_rot_list; p; p = p->next)
    {
        if ((*cfunc)(p->rot, data) == 0)
        {
            return RIG_OK;
        }
    }

    return RIG_OK;
}
/** @} */ /* rot_internal definitions */


/**
 * \addtogroup rotator
 * @{
 */

/**
 * \brief Allocate a new #ROT handle.
 *
 * \param rot_model The rotator model for this new handle.
 *
 * Allocates a new #ROT handle and initializes the associated data
 * for \a rot_model (see rotlist.h or `rigctl -l`).
 *
 * \return a pointer to the #ROT handle otherwise NULL if memory allocation
 * failed or \a rot_model is unknown, e.g. backend autoload failed.
 *
 * \sa rot_cleanup(), rot_open()
 */
ROT *HAMLIB_API rot_init(rot_model_t rot_model)
{
    ROT *rot;
    const struct rot_caps *caps;
    struct rot_state *rs;
    hamlib_port_t *rotp, *rotp2;

    rot_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);

    rot_check_backend(rot_model);

    caps = rot_get_caps(rot_model);

    if (!caps)
    {
        return NULL;
    }

    /*
     * okay, we've found it. Allocate some memory and set it to zeros,
     * and especially the initialize the callbacks
     */
    rot = calloc(1, sizeof(ROT));

    if (rot == NULL)
    {
        /*
         * FIXME: how can the caller know it's a memory shortage,
         *        and not "rot not found" ?
         */
        return NULL;
    }

    /* caps is const, so we need to tell compiler
       that we know what we are doing */
    rot->caps = (struct rot_caps *) caps;

    /*
     * populate the rot->state
     */
    /**
     * \todo Read the Preferences here!
     */
    rs = ROTSTATE(rot);

    //TODO Allocate new rotport[2]
    // For now, use the embedded ones
    rotp = ROTPORT(rot);
    rotp2 = ROTPORT2(rot);

    rs->comm_state = 0;
    rotp->type.rig = rotp2->type.rig = caps->port_type; /* default from caps */

    rotp->write_delay = rotp2->write_delay = caps->write_delay;
    rotp->post_write_delay = rotp2->post_write_delay = caps->post_write_delay;
    rotp->timeout = rotp2->timeout = caps->timeout;
    rotp->retry = rotp2->retry = caps->retry;

    switch (caps->port_type)
    {
    case RIG_PORT_SERIAL:
        strncpy(rotp->pathname, DEFAULT_SERIAL_PORT, HAMLIB_FILPATHLEN - 1);
        rotp->parm.serial.rate = rotp2->parm.serial.rate =
                                     caps->serial_rate_max;   /* fastest ! */
        rotp->parm.serial.data_bits = rotp2->parm.serial.data_bits =
                                          caps->serial_data_bits;
        rotp->parm.serial.stop_bits = rotp2->parm.serial.stop_bits =
                                          caps->serial_stop_bits;
        rotp->parm.serial.parity = rotp2->parm.serial.parity =
                                       caps->serial_parity;
        rotp->parm.serial.handshake = rotp2->parm.serial.handshake =
                                          caps->serial_handshake;
        break;

    case RIG_PORT_PARALLEL:
        strncpy(rotp->pathname, DEFAULT_PARALLEL_PORT, HAMLIB_FILPATHLEN - 1);
        break;

    case RIG_PORT_NETWORK:
    case RIG_PORT_UDP_NETWORK:
        strncpy(rotp->pathname, "127.0.0.1:4533", HAMLIB_FILPATHLEN - 1);
        break;

    default:
        strncpy(rotp->pathname, "", HAMLIB_FILPATHLEN - 1);
    }

    rs->min_el = caps->min_el;
    rs->max_el = caps->max_el;
    rs->min_az = caps->min_az;
    rs->max_az = caps->max_az;
    rs->current_speed = 50; // Set default speed to 50%

    rotp->fd = -1;

    rs->has_get_func = caps->has_get_func;
    rs->has_set_func = caps->has_set_func;
    rs->has_get_level = caps->has_get_level;
    rs->has_set_level = caps->has_set_level;
    rs->has_get_parm = caps->has_get_parm;
    rs->has_set_parm = caps->has_set_parm;

    rs->has_status = caps->has_status;

    memcpy(rs->level_gran, caps->level_gran, sizeof(gran_t)*RIG_SETTING_MAX);
    memcpy(rs->parm_gran, caps->parm_gran, sizeof(gran_t)*RIG_SETTING_MAX);

    /*
     * let the backend a chance to setup his private data
     * This must be done only once defaults are setup,
     * so the backend init can override rot_state.
     */
    if (caps->rot_init != NULL)
    {
        int retcode = caps->rot_init(rot);

        if (retcode != RIG_OK)
        {
            rot_debug(RIG_DEBUG_VERBOSE,
                      "%s: backend_init failed!\n",
                      __func__);
            /* cleanup and exit */
            free(rot);
            return NULL;
        }
    }

    // Now we have to copy our new rig state hamlib_port structure to the deprecated one
    // Clients built on older 4.X versions will use the old structure
    // Clients built on newer 4.5 versions will use the new structure
    memcpy(&rs->rotport_deprecated, rotp,
           sizeof(rs->rotport_deprecated));

    return rot;
}


/**
 * \brief Open the communication channel to the rotator.
 *
 * \param rot The #ROT handle of the rotator to be opened.
 *
 * Opens the communication channel to a rotator for which the #ROT handle has
 * been passed.
 *
 * \return RIG_OK if the operation has been successful, otherwise a **negative
 * value** if an error occurred (in which case, cause is set appropriately).
 *
 * \retval RIG_OK Communication channel successfully opened.
 * \retval -RIG_EINVAL \a rot is NULL or inconsistent.
 * \retval -RIG_ENIMPL Communication port type is not implemented yet.
 *
 * \sa rot_init(), rot_close()
 */
int HAMLIB_API rot_open(ROT *rot)
{
    const struct rot_caps *caps;
    struct rot_state *rs;
    hamlib_port_t *rotp = ROTPORT(rot);
    hamlib_port_t *rotp2 = ROTPORT2(rot);
    int status;
    int net1, net2, net3, net4, port;
    deferred_config_item_t *item;

    rot_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);

    if (!rot || !rot->caps)
    {
        return -RIG_EINVAL;
    }

    caps = rot->caps;
    rs = ROTSTATE(rot);

    if (rs->comm_state)
    {
        return -RIG_EINVAL;
    }

    rotp->fd = -1;
    rotp2->fd = -1;

    // determine if we have a network address
    if (sscanf(rotp->pathname, "%d.%d.%d.%d:%d", &net1, &net2, &net3, &net4,
               &port) == 5)
    {
        char *type = "TCP";

        if (rot->caps->port_type == RIG_PORT_UDP_NETWORK)
        {
            rotp->type.rig = RIG_PORT_UDP_NETWORK;
            type = "UDP";
        }
        else
        {
            rotp->type.rig = RIG_PORT_NETWORK;
        }

        rig_debug(RIG_DEBUG_TRACE, "%s: using network address %s:%s\n", __func__,
                  rotp->pathname, type);
    }

    if (sscanf(rotp2->pathname, "%d.%d.%d.%d:%d", &net1, &net2, &net3, &net4,
               &port) == 5)
    {
        rig_debug(RIG_DEBUG_TRACE, "%s: using network address %s\n", __func__,
                  rotp2->pathname);

        if (rot->caps->port_type == RIG_PORT_UDP_NETWORK)
        {
            rotp->type.rig = RIG_PORT_UDP_NETWORK;
        }
        else
        {
            rotp->type.rig = RIG_PORT_NETWORK;
        }
    }

    switch (rotp->type.rig)
    {
    case RIG_PORT_SERIAL:
        status = serial_open(rotp);

        if (status != 0)
        {
            return status;
        }

        // RT21 has 2nd serial port elevation
        // so if a 2nd pathname is provided we'll open it
        if (rot->caps->rot_model == ROT_MODEL_RT21 && rotp2->pathname[0] != 0)
        {
            status = serial_open(rotp2);

            if (status != 0)
            {
                return status;
            }
        }

        break;

    case RIG_PORT_PARALLEL:
        status = par_open(rotp);

        if (status < 0)
        {
            return status;
        }

        break;

    case RIG_PORT_DEVICE:
        status = open(rotp->pathname, O_RDWR, 0);

        if (status < 0)
        {
            return -RIG_EIO;
        }

        rotp->fd = status;

        // RT21 has 2nd serial port elevation
        // so if a 2nd pathname is provided we'll open it
        if (rot->caps->rot_model == ROT_MODEL_RT21 && rotp2->pathname[0] != 0)
        {
            status = open(rotp2->pathname, O_RDWR, 0);

            if (status < 0)
            {
                return -RIG_EIO;
            }

            rotp2->fd = status;
        }

        break;

#if defined(HAVE_LIB_USB_H) || defined(HAMB_LIBUSB_1_0_LIBUSB_H)

    case RIG_PORT_USB:
        status = usb_port_open(rotp);

        if (status < 0)
        {
            return status;
        }

        break;
#endif

    case RIG_PORT_NONE:
    case RIG_PORT_RPC:
        break;  /* ez :) */

    case RIG_PORT_NETWORK:
    case RIG_PORT_UDP_NETWORK:
        /* FIXME: default port */
        status = network_open(rotp, 4533);

        if (status < 0)
        {
            return status;
        }

        break;

    default:
        return -RIG_EINVAL;
    }

    add_opened_rot(rot);

    if (rotp->type.rig != RIG_PORT_NETWORK
            && rotp->type.rig != RIG_PORT_UDP_NETWORK)
    {
        if (rotp->parm.serial.dtr_state == RIG_SIGNAL_ON)
        {
            ser_set_dtr(rotp, 1);
        }
        else
        {
            ser_set_dtr(rotp, 0);
        }

        if (rotp->parm.serial.rts_state == RIG_SIGNAL_ON)
        {
            ser_set_rts(rotp, 1);
        }
        else
        {
            ser_set_rts(rotp, 0);
        }
    }

    rs->comm_state = 1;

    /*
     * Now that the rotator port is officially opened, we can
     *  send the deferred configuration info.
     */
    while ((item = rs->config_queue.firstt))
    {
        rs->config_queue.firstt = item->nextt;
        status = rot_set_conf(rot, item->token, item->value);
        free(item->value);
        free(item);

        if (status != RIG_OK)
        {
            return status;
        }
    }

    /*
     * Maybe the backend has something to initialize
     * In case of failure, just close down and report error code.
     */
    if (caps->rot_open != NULL)
    {
        status = caps->rot_open(rot);

        if (status != RIG_OK)
        {
            memcpy(&rs->rotport_deprecated, rotp,
                   sizeof(rs->rotport_deprecated));
            return status;
        }
    }

    memcpy(&rs->rotport_deprecated, rotp,
           sizeof(rs->rotport_deprecated));

    return RIG_OK;
}


/**
 * \brief Close the communication channel to the rotator.
 * \param rot The #ROT handle of the rotator to be closed.
 *
 * Closes the communication channel to a rotator for which #ROT handle has
 * been passed by argument that was previously opened with rot_open().
 *
 * \return RIG_OK if the operation has been successful, otherwise a **negative
 * value** if an error occurred (in which case, cause is set appropriately).
 *
 * \retval RIG_OK Communication channel successfully closed.
 * \retval -RIG_EINVAL \a rot is NULL or inconsistent.
 *
 * \sa rot_cleanup(), rot_open()
 */
int HAMLIB_API rot_close(ROT *rot)
{
    const struct rot_caps *caps;
    struct rot_state *rs;
    hamlib_port_t *rotp = ROTPORT(rot);

    rot_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);

    if (!rot || !rot->caps)
    {
        return -RIG_EINVAL;
    }

    caps = rot->caps;
    rs = ROTSTATE(rot);

    if (!rs->comm_state)
    {
        return -RIG_EINVAL;
    }

    /*
     * Let the backend say 73s to the rot.
     * and ignore the return code.
     */
    if (caps->rot_close)
    {
        caps->rot_close(rot);
    }


    if (rotp->fd != -1)
    {
        switch (rotp->type.rig)
        {
        case RIG_PORT_SERIAL:
            ser_close(rotp);
            break;

        case RIG_PORT_PARALLEL:
            par_close(rotp);
            break;

#if defined(HAVE_LIB_USB_H) || defined(HAMB_LIBUSB_1_0_LIBUSB_H)

        case RIG_PORT_USB:
            usb_port_close(rotp);
            break;
#endif

        case RIG_PORT_NETWORK:
        case RIG_PORT_UDP_NETWORK:
            network_close(rotp);
            break;

        default:
            close(rotp->fd);
        }

        rotp->fd = -1;
    }

    remove_opened_rot(rot);

    rs->comm_state = 0;

    memcpy(&rs->rotport_deprecated, rotp,
           sizeof(rs->rotport_deprecated));

    return RIG_OK;
}


/**
 * \brief Release a #ROT handle and free associated memory.
 *
 * \param rot The #ROT handle to be released.
 *
 * Releases a #ROT handle for which the communication channel has been closed
 * with rot_close().
 *
 * \return RIG_OK if the operation has been successful, otherwise a **negative
 * value** if an error occurred (in which case, cause is set appropriately).
 *
 * \retval RIG_OK #ROT handle successfully released.
 * \retval -RIG_EINVAL \a rot is NULL or inconsistent.
 *
 * \sa rot_init(), rot_close()
 */
int HAMLIB_API rot_cleanup(ROT *rot)
{
    rot_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);

    if (!rot || !rot->caps)
    {
        return -RIG_EINVAL;
    }

    /*
     * check if they forgot to close the rot
     */
    if (ROTSTATE(rot)->comm_state)
    {
        rot_close(rot);
    }

    /*
     * basically free up the priv struct
     */
    if (rot->caps->rot_cleanup)
    {
        rot->caps->rot_cleanup(rot);
    }

    //TODO Release any allocated port structures

    free(rot);

    return RIG_OK;
}


/**
 * \brief Set the azimuth and elevation of the rotator.
 *
 * \param rot The #ROT handle.
 * \param azimuth The azimuth to set in decimal degrees.
 * \param elevation The elevation to set in decimal degrees.
 *
 * Sets the azimuth and elevation of the rotator.
 *
 * \b Note: A given rotator may be capable of setting only the azimuth or
 * only the elevation or both.  The rotator backend will ignore the unneeded
 * parameter.
 *
 * \return RIG_OK if the operation has been successful, otherwise a **negative
 * value** if an error occurred (in which case, cause is set appropriately).
 *
 * \retval RIG_OK Either or both parameters set successfully.
 * \retval -RIG_EINVAL \a rot is NULL or inconsistent \b or either \a azimuth
 * or \a elevation is out of range for this rotator.
 * \retval -RIG_ENAVAIL rot_caps#set_position() capability is not available.
 *
 * \sa rot_get_position()
 */
int HAMLIB_API rot_set_position(ROT *rot,
                                azimuth_t azimuth,
                                elevation_t elevation)
{
    const struct rot_caps *caps;
    const struct rot_state *rs;

    rot_debug(RIG_DEBUG_VERBOSE, "%s called az=%.02f el=%.02f\n", __func__, azimuth,
              elevation);

    if (CHECK_ROT_ARG(rot))
    {
        return -RIG_EINVAL;
    }

    caps = rot->caps;
    rs = ROTSTATE(rot);

    azimuth += rs->az_offset;
    elevation += rs->el_offset;

    rot_debug(RIG_DEBUG_VERBOSE, "%s: south_zero=%d \n", __func__, rs->south_zero);

    if (rs->south_zero)
    {
        azimuth += azimuth >= 180 ? -180 : 180;
        rot_debug(RIG_DEBUG_TRACE, "%s: south adj to az=%.2f\n", __func__, azimuth);
    }

    if (azimuth < rs->min_az
            || azimuth > rs->max_az
            || elevation < rs->min_el
            || elevation > rs->max_el)
    {
        rot_debug(RIG_DEBUG_TRACE,
                  "%s: range problem az=%.02f(min=%.02f,max=%.02f), el=%02f(min=%.02f,max=%02f)\n",
                  __func__, azimuth, rs->min_az, rs->max_az, elevation, rs->min_el, rs->max_el);
        return -RIG_ELIMIT;
    }

    if (caps->set_position == NULL)
    {
        return -RIG_ENAVAIL;
    }

    return caps->set_position(rot, azimuth, elevation);
}


/**
 * \brief Query the azimuth and elevation of the rotator.
 *
 * \param rot The #ROT handle.
 * \param azimuth The variable to store the current azimuth.
 * \param elevation The variable to store the current elevation
 *
 * Retrieves the current azimuth and elevation values of the rotator.  The
 * stored values are in decimal degrees.
 *
 * \b Note: A given rotator may be capable of querying only the azimuth or
 * only the elevation or both.  The rotator backend should store a value of 0
 * in the unsupported variable.
 *
 * \return RIG_OK if the operation has been successful, otherwise a **negative
 * value** if an error occurred (in which case, cause is set appropriately).
 *
 * \retval RIG_OK Either or both parameters queried and stored successfully.
 * \retval -RIG_EINVAL \a rot is NULL or inconsistent.
 * \retval -RIG_ENAVAIL rot_caps#get_position() capability is not available.
 *
 * \sa rot_set_position()
 */
int HAMLIB_API rot_get_position(ROT *rot,
                                azimuth_t *azimuth,
                                elevation_t *elevation)
{
    const struct rot_caps *caps;
    const struct rot_state *rs;
    azimuth_t az;
    elevation_t el;
    int retval;

    rot_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);

    if (CHECK_ROT_ARG(rot) || !azimuth || !elevation)
    {
        return -RIG_EINVAL;
    }

    caps = rot->caps;
    rs = ROTSTATE(rot);

    if (caps->get_position == NULL)
    {
        return -RIG_ENAVAIL;
    }

    retval = caps->get_position(rot, &az, &el);

    if (retval != RIG_OK) { return retval; }

    rot_debug(RIG_DEBUG_VERBOSE, "%s: got az=%.2f, el=%.2f\n", __func__, az, el);

    if (rs->south_zero)
    {
        az += az >= 180 ? -180 : 180;
        rot_debug(RIG_DEBUG_VERBOSE, "%s: south adj to az=%.2f\n", __func__, az);
    }

    *azimuth = az - rs->az_offset;
    *elevation = el - rs->el_offset;

    return RIG_OK;
}


/**
 * \brief Park the rotator.
 *
 * \param rot The #ROT handle.
 *
 * Park the rotator in a predetermined position as implemented by the rotator
 * hardware.
 *
 * \return RIG_OK if the operation has been successful, otherwise a **negative
 * value** if an error occurred (in which case, cause is set appropriately).
 *
 * \retval RIG_OK The rotator was parked successfully.
 * \retval -RIG_EINVAL \a rot is NULL or inconsistent.
 * \retval -RIG_ENAVAIL rot_caps#park() capability is not available.
 *
 */
int HAMLIB_API rot_park(ROT *rot)
{
    const struct rot_caps *caps;

    rot_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);

    if (CHECK_ROT_ARG(rot))
    {
        return -RIG_EINVAL;
    }

    caps = rot->caps;

    if (caps->park == NULL)
    {
        return -RIG_ENAVAIL;
    }

    return caps->park(rot);
}


/**
 * \brief Stop the rotator.
 *
 * \param rot The #ROT handle.
 *
 * Stop the rotator.  Command should be immediate.
 *
 * \return RIG_OK if the operation has been successful, otherwise a **negative
 * value** if an error occurred (in which case, cause is set appropriately).
 *
 * \retval RIG_OK The rotator was stopped successfully.
 * \retval -RIG_EINVAL \a rot is NULL or inconsistent.
 * \retval -RIG_ENAVAIL rot_caps#stop() capability is not available.
 *
 * \sa rot_move()
 */
int HAMLIB_API rot_stop(ROT *rot)
{
    const struct rot_caps *caps;

    rot_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);

    if (CHECK_ROT_ARG(rot))
    {
        return -RIG_EINVAL;
    }

    caps = rot->caps;

    if (caps->stop == NULL)
    {
        return -RIG_ENAVAIL;
    }

    return caps->stop(rot);
}


/**
 * \brief Reset the rotator.
 *
 * \param rot The #ROT handle.
 * \param reset The reset operation to perform
 *
 * Resets the rotator to a state determined by \a reset.
 *
 * \return RIG_OK if the operation has been successful, otherwise a **negative
 * value** if an error occurred (in which case, cause is set appropriately).
 *
 * \retval RIG_OK The rotator was reset successfully.
 * \retval -RIG_EINVAL \a rot is NULL or inconsistent.
 * \retval -RIG_ENAVAIL rot_caps#reset() capability is not available.
 */
int HAMLIB_API rot_reset(ROT *rot, rot_reset_t reset)
{
    const struct rot_caps *caps;

    rot_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);

    if (CHECK_ROT_ARG(rot))
    {
        return -RIG_EINVAL;
    }

    caps = rot->caps;

    if (caps->reset == NULL)
    {
        return -RIG_ENAVAIL;
    }

    return caps->reset(rot, reset);
}


/**
 * \brief Move the rotator in the specified direction and speed.
 *
 * \param rot The #ROT handle.
 * \param direction Direction of movement.
 * \param speed Speed of movement.
 *
 * Move the rotator in the specified direction.  The \a direction is one of
 * #ROT_MOVE_CCW, #ROT_MOVE_CW, #ROT_MOVE_LEFT, #ROT_MOVE_RIGHT, #ROT_MOVE_UP,
 * #ROT_MOVE_DOWN, #ROT_MOVE_UP_LEFT, #ROT_MOVE_CCW, #ROT_MOVE_UP_RIGHT, #ROT_MOVE_UP_CW,
 * #ROT_MOVE_DOWN_LEFT, #ROT_MOVE_DOWN_CCW, #ROT_MOVE_DOWN_RIGHT, #ROT_MOVE_DOWN_CW.
 * The \a speed is a value between 1 and 100 or #ROT_SPEED_NOCHANGE.
 *
 * \retval RIG_OK The rotator move was successful.
 * \retval -RIG_EINVAL \a rot is NULL or inconsistent.
 * \retval -RIG_ENAVAIL rot_caps#move() capability is not available.
 *
 * \sa rot_stop()
 */
int HAMLIB_API rot_move(ROT *rot, int direction, int speed)
{
    const struct rot_caps *caps;

    rot_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);

    if (CHECK_ROT_ARG(rot))
    {
        return -RIG_EINVAL;
    }

    caps = rot->caps;

    if (caps->move == NULL)
    {
        return -RIG_ENAVAIL;
    }

    return caps->move(rot, direction, speed);
}


/**
 * \brief Get general information from the rotator.
 *
 * \param rot The #ROT handle.
 *
 * Retrieves some general information from the rotator.  This can include
 * firmware revision, exact model name, or just nothing.
 *
 * \return A pointer to static memory containing an ASCII nul terminated
 * string (C string) if the operation has been successful, otherwise NULL if
 * \a rot is NULL or inconsistent or the rot_caps#get_info() capability is not
 * available.
 */
const char *HAMLIB_API rot_get_info(ROT *rot)
{
    rot_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);

    if (CHECK_ROT_ARG(rot))
    {
        return NULL;
    }

    if (rot->caps->get_info == NULL)
    {
        return NULL;
    }

    return rot->caps->get_info(rot);
}


/**
 * \brief Query status flags of the rotator.
 *
 * \param rot The #ROT handle.
 * \param status The variable where the status flags will be stored.
 *
 * Query the active status flags from the rotator.
 *
 * \return RIG_OK if the operation has been successful, otherwise a **negative
 * value** if an error occurred (in which case, cause is set appropriately).
 *
 * \retval RIG_OK The query was successful.
 * \retval -RIG_EINVAL \a rot is NULL or inconsistent.
 * \retval -RIG_ENAVAIL rot_caps#get_status() capability is not available.
 */
int HAMLIB_API rot_get_status(ROT *rot, rot_status_t *status)
{
    rot_debug(RIG_DEBUG_VERBOSE, "%s called\n", __func__);

    if (CHECK_ROT_ARG(rot))
    {
        return -RIG_EINVAL;
    }

    if (rot->caps->get_status == NULL)
    {
        return -RIG_ENAVAIL;
    }

    return rot->caps->get_status(rot, status);
}

/**
 * \brief Get the address of rotator data structure(s)
 *
 * \sa amp_data_pointer(), rig_data_pointer()
 *
 */
void *HAMLIB_API rot_data_pointer(ROT *rot, rig_ptrx_t idx)
{
    switch (idx)
    {
    case RIG_PTRX_ROTPORT:
        return ROTPORT(rot);

    case RIG_PTRX_ROTPORT2:
        return ROTPORT2(rot);

    case RIG_PTRX_ROTSTATE:
        return ROTSTATE(rot);

    default:
        rot_debug(RIG_DEBUG_ERR, "%s: Invalid data index=%d\n", __func__, idx);
        return NULL;
    }
}
/*! @} */
