/*
 * Author: Jon Trulson <jtrulson@ics.com>
 * Copyright (c) 2015-2016 Intel Corporation.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#pragma once

#include <string>
#include <map>

#include "Manager.h"
#include "Notification.h"
#include "Options.h"
#include "Driver.h"
#include "Node.h"
#include "Group.h"
#include "platform/Log.h"

namespace upm {
  
  /**
   * @brief OpenZWave Devices Library
   * @defgroup ozw libupm-ozw
   * @ingroup uart wifi
   */

  /**
   * @library ozw
   * @sensor ozw
   * @comname Wrapper for the OpenZWave Library
   * @type wifi
   * @man other
   * @con uart
   * @web http://www.openzwave.com/
   *
   * @brief UPM API for the OpenZWave library
   *
   * This module implements a singleton wrapper around the OpenZWave
   * library.  OpenZWave must be compiled and installed on your
   * machine in order to use this library.
   *
   * This module was developed with OpenZWave 1.3/1.4, and an Aeon
   * Z-Stick Gen5 configured as a Primary Controller.  It provides the
   * ability to query and set various values that can be used to
   * control ZWave devices.  It does not concern itself with
   * configuration of devices.  It is assumed that you have already
   * setup your ZWave network using a tool like the OpenZWave control
   * panel, and have already configured your devices as appropriate.
   *
   * To avoid exposing some of the internals of OpenZWave, devices
   * (nodes) and their values, are accessed via a nodeId and a value
   * index number.  The ozwdump example will run dumpNodes() which
   * will list the currently connected devices and the values that are
   * available to them, along with an index number for that value.  It
   * is through these values (nodeId and index) that you can query and
   * set device values at a low level.
   *
   * In addition to querying values from a device (such as state
   * (on/off), or temperature, etc), methods are provided to allow you
   * to control these devices to the extent they allow, for example,
   * using a ZWave connected switch to turn on a lamp.
   *
   * Access to this class by OZW drivers is handled by the
   * ozwInterface class.  It is that class that drivers use for access
   * to ozw, and therefore the Z-Wave network.
   *
   * This class is not intended to be used directly by end users.
   * When writing an OZW driver, the ozwInterface class should be used
   * (inherited) by your driver, and your driver should wrap and
   * expose only those methods needed by the user.  Take a look at
   * some of the drivers (like aeotecss6) to see how this works.
   */

  // forward declaration of private zwNode data
  class zwNode;

  class OZW {
  public:

    typedef std::map<uint8_t, zwNode *> zwNodeMap_t;

    /**
     * Get our singleton instance, initializing it if neccessary.  All
     * requests to this class should be done through this instance
     * accessor.
     *
     * @return static pointer to our class instance
     */
    static OZW* instance();

    /**
     * Start configuration with basic options.  This must be called
     * prior to init(), after the OZW() constructor is called.
     *
     * @param configPath Set the location of the OpenZWave config
     * directory, default is /etc/openzwave
     * @param userConfigDir Set the path to the user configuration
     * directory.  This is the location of the zwcfg*.xml and
     * option.xml files for the user (probably created by the
     * OpenZWave Control Panel example application).  The default is
     * the current directory ("").
     * @param cmdLine Specify command line formatted options to
     * OpenZWave.  The default is "".
     */
    void optionsCreate(std::string configPath="/etc/openzwave", 
                       std::string userConfigDir="",
                       std::string cmdLine="");
    
    /**
     * Add an integer Option.  See the OpenZWave documentation for
     * valid values.
     *
     * @param name The name of the configuration option
     * @param val The value to set it to
     */
    void optionAddInt(std::string name, int val);

    /**
     * Add a boolean Option.  See the OpenZWave documentation for
     * valid values.
     *
     * @param name The name of the configuration option
     * @param val The value to set it to
     */
    void optionAddBool(std::string name, bool val);

    /**
     * Add a string Option.  See the OpenZWave documentation for valid
     * values.
     *
     * @param name The name of the configuration option
     * @param val The value to set it to
     * @append true to append to the option, false to override
     */
    void optionAddString(std::string name, std::string val, bool append);

    /**
     * Lock the Options.  This must be called after all options have
     * been set, and before init() is called.  If init() is called
     * without locking the Options, init() will lock them itself.
     * After the options have been locked, no further options can be
     * specified.
     */
    void optionsLock();

    /**
     * Initialize the ZWave network.  This method will start a probe
     * of all defined devices on the ZWave network and query essential
     * information about them.  This function will not return until
     * either initialization has failed, or has succeeded far enough
     * along for the following methods to work.  Depending on the size
     * an complexity of the ZWave network, this may take anywhere from
     * seconds to several minutes to complete.
     *
     * All Options (via option*()) must have been specified before
     * this function is called.  If the Options have not been locked
     * via optionsLock() prior to calling init(), this method will
     * lock them for you before proceeding.
     *
     * @param devicePath The device path for the ZWave controller,
     * typically something like /dev/ttyACM0, or similiar
     * @param isHID true if this is a HID device, false otherwise (ie:
     * a serial port like /dev/ttyACM0, /dev/ttyUSB0, etc).  Default
     * is false.
     * @return true if init succeeded, false otherwise
     */
    bool init(std::string devicePath, bool isHID=false);

    /**
     * Dump information about all configured nodes (devices) and their
     * available values to stdout.  This is useful to determine what
     * nodes are available, and the index (used for querying and
     * seting values for them).  In addition, it includes information
     * about each value (type, current value, etc).
     *
     * @param all set to true to dump information about all values
     * available for each node.  If false, only information about
     * 'user' values (ignoring 'system' and 'configuration') are
     * output.  The default is false ('user' values only).
     */
    void dumpNodes(bool all=false);

    /**
     * Return a string (which may be empty) indicating the Units of
     * measure for a given value.  For example, querying a temperature
     * value may return "F" to indicate Fahrenheit.
     *
     * @param nodeId The node ID to query
     * @param index The value index (see dumpNodes()) of the value to query.
     * @return A string containing the Unit of measure for the value
     */
    std::string getValueUnits(int nodeId, int index);

    /**
     * Set the text for the Units of measure for a value.
     *
     * @param nodeId The node ID to query
     * @param index The value index (see dumpNodes()) of the value to query.
     * @param text The text to set
     */
    void setValueUnits(int nodeId, int index, std::string text);

    /**
     * Return a string (which may be empty) containing the
     * user-freindly Label for a value.
     *
     * @param nodeId The node ID to query
     * @param index The value index (see dumpNodes()) of the value to query.
     * @return A string containing the Value's label
     */
    std::string getValueLabel(int nodeId, int index);

    /**
     * Set the text for a Value's label.
     *
     * @param nodeId The node ID to query
     * @param index The value index (see dumpNodes()) of the value to query.
     * @param text The text to set
     */
    void setValueLabel(int nodeId, int index, std::string text);

    /**
     * Return a string (which may be empty) indicating the Help text
     * of a value, if available.
     *
     * @param nodeId The node ID to query
     * @param index The value index (see dumpNodes()) of the value to query.
     * @return A string containing the Help text, if available
     */
    std::string getValueHelp(int nodeId, int index);

    /**
     * Set the text for a Value's help text.
     *
     * @param nodeId The node ID to query
     * @param index The value index (see dumpNodes()) of the value to query.
     * @param text The text to set
     */
    void setValueHelp(int nodeId, int index, std::string text);

    /**
     * Set the contents of a Value to a string.  This should always
     * succeed if the supplied content makes sense for a given value,
     * regardless of the value's actual type.
     *
     * @param nodeId The node ID
     * @param index The value index (see dumpNodes()) of the value to query.
     * @param val the content to assign to the value referenced by
     * nodeId, and index.
     */
    void setValueAsString(int nodeId, int index, std::string val);

    /**
     * Set the contents of a Value, to a bool.  This will fail, and an
     * error message printed if the value type is not a boolean value.
     *
     * @param nodeId The node ID
     * @param index The value index (see dumpNodes()) of the value to query.
     * @param val the boolean content to assign to the value referenced
     * by nodeId, and index.
     */
    void setValueAsBool(int nodeId, int index, bool val);

    /**
     * Set the contents of a Value, to a byte.  This will fail, and an
     * error message printed if the value type is not a byte value.
     *
     * @param nodeId The node ID
     * @param index The value index (see dumpNodes()) of the value to query.
     * @param val the byte content to assign to the value referenced
     * by nodeId, and index.
     */
    void setValueAsByte(int nodeId, int index, uint8_t val);

    /**
     * Set the contents of a Value, to a float.  This will fail, and an
     * error message printed if the value type is not a float value.
     *
     * @param nodeId The node ID
     * @param index The value index (see dumpNodes()) of the value to query.
     * @param val the float content to assign to the value referenced
     * by nodeId, and index.
     */
    void setValueAsFloat(int nodeId, int index, float val);

    /**
     * Set the contents of a Value, to a 32 bit integer (int32).  This
     * will fail, and an error message printed if the value type is
     * not an int32.
     *
     * @param nodeId The node ID
     * @param index The value index (see dumpNodes()) of the value to query.
     * @param val the int32 content to assign to the value referenced
     * by nodeId, and index.
     */
    void setValueAsInt32(int nodeId, int index, int32_t val);

    /**
     * Set the contents of a Value, to a 16 bit integer (int16).  This
     * will fail, and an error message printed if the value type is
     * not an int16.
     *
     * @param nodeId The node ID
     * @param index The value index (see dumpNodes()) of the value to query.
     * @param val the int16 content to assign to the value referenced
     * by nodeId, and index.
     */
    void setValueAsInt16(int nodeId, int index, int16_t val);

    /**
     * Set the contents of a Value, to an array of bytes.  This will
     * fail, and an error message printed if the value type is not
     * settable as an array of bytes.
     *
     * @param nodeId The node ID
     * @param index The value index (see dumpNodes()) of the value to query.
     * @param val the byte array content to assign to the value referenced
     * by nodeId, and index.
     * @param len The length of the byte array
     */
    void setValueAsBytes(int nodeId, int index, uint8_t *val, uint8_t len);

    /**
     * Get the minimum allowed value for a node's Value.
     *
     * @param nodeId The node ID
     * @param index The value index (see dumpNodes()) of the value to query.
     * @return The minumum allowed value
     */
    int getValueMin(int nodeId, int index);

    /**
     * Get the maximum allowed value for a node's Value.
     *
     * @param nodeId The node ID
     * @param index The value index (see dumpNodes()) of the value to query.
     * @return The maximum allowed value
     */
    int getValueMax(int nodeId, int index);

    /**
     * Test whether a value is read-only.
     *
     * @param nodeId The node ID
     * @param index The value index (see dumpNodes()) of the value to query.
     * @return true if the value is read-only, false otherwise
     */
    bool isValueReadOnly(int nodeId, int index);

    /**
     * Test whether a value is write only.
     *
     * @param nodeId The node ID
     * @param index The value index (see dumpNodes()) of the value to query.
     * @return true if the value is write-only, false otherwise
     */
    bool isValueWriteOnly(int nodeId, int index);

    /**
     * Test whether a value is really set on a node, and not a default
     * value chosen by the OpenZWave library.
     *
     * @param nodeId The node ID
     * @param index The value index (see dumpNodes()) of the value to query.
     * @return true if the value is really set, false if a default value is
     * being reported
     */
    bool isValueSet(int nodeId, int index);

    /**
     * Test whether a value is being manually polled by the OpenZWave
     * library.  Most modern devices are never polled, rather they are
     * configured to report changing values to the controller on their
     * own at device specific intervals or when appropriate events
     * (depending the device) have occurred.
     *
     * @param nodeId The node ID
     * @param index The value index (see dumpNodes()) of the value to query.
     * @return true if the value is being maually polled, false otherwise
     * being reported
     */
    bool isValuePolled(int nodeId, int index);

    /**
     * Return the content of a value as a string.  This should always
     * succeed, regardless of the actual value type.
     *
     * @param nodeId The node ID
     * @param index The value index (see dumpNodes()) of the value to query.
     * @return A string representing the current contents of a value.
     */
    std::string getValueAsString(int nodeId, int index);

    /**
     * Return the content of a value as a booleang.  This will fail,
     * and an error message printed if the value type is not boolean.
     *
     * @param nodeId The node ID
     * @param index The value index (see dumpNodes()) of the value to query.
     * @return A boolean representing the current contents of a value.
     */
    bool getValueAsBool(int nodeId, int index);

    /**
     * Return the content of a value as a byte.  This will fail, and
     * an error message printed if the value type is not a byte.
     *
     * @param nodeId The node ID
     * @param index The value index (see dumpNodes()) of the value to query.
     * @return A byte representing the current contents of a value.
     */
    uint8_t getValueAsByte(int nodeId, int index);

    /**
     * Return the content of a value as a float.  This will fail, and
     * an error message printed if the value type is not a floating
     * point value.
     *
     * @param nodeId The node ID
     * @param index The value index (see dumpNodes()) of the value to query.
     * @return A float representing the current contents of a value.
     */
    float getValueAsFloat(int nodeId, int index);

    /**
     * Return the content of a value as an int32.  This will fail, and
     * an error message printed if the value type is not an int32.
     *
     * @param nodeId The node ID
     * @param index The value index (see dumpNodes()) of the value to query.
     * @return An int32 representing the current contents of a value.
     */
    int getValueAsInt32(int nodeId, int index);

    /**
     * Return the content of a value as an int16.  This will fail, and
     * an error message printed if the value type is not an int16.
     *
     * @param nodeId The node ID
     * @param index The value index (see dumpNodes()) of the value to query.
     * @return An int16 representing the current contents of a value.
     */
    int getValueAsInt16(int nodeId, int index);

    /**
     * Issue a refresh request for a value on a node.  OpenZWave will
     * query the value and update it's internal state when the device
     * responds.  Note, this happens asynchronously - it may take some
     * time before the current value is reported to OpenZWave by the
     * node.  If the node is asleep, you may not get a current value
     * for some time (or at all, depending on the device).  This
     * method will return immediately after the request has been
     * queued.
     *
     * @param nodeId The node ID
     * @param index The value index (see dumpNodes()) of the value to query.
     */
    void refreshValue(int nodeId, int index);

    /**
     * Enable or disable some debugging output.  Note, this will not
     * affect OpenZWave's own debugging, which is usually set in the
     * option.xml file.
     *
     * @param enable true to enable debugging, false otherwise
     */
    void setDebug(bool enable);

    /**
     * Determine if a node is a listening device -- in other words, the
     * node never sleeps.
     *
     * @param nodeId The node ID
     * @return true if the node never sleeps, false otherwise
     */
    bool isNodeListeningDevice(int nodeId);

    /**
     * Determine if a node is a frequent listening device -- in other
     * words, if the node is asleep, can it be woken by a beam.
     *
     * @param nodeId The node ID
     * @return true if the node is a frequent listening device, false
     * otherwise
     */
    bool isNodeFrequentListeningDevice(int nodeId);

    /**
     * Determine if a node is awake.
     *
     * @param nodeId The node ID
     * @return true if the node is awake, false otherwise
     */
    bool isNodeAwake(int nodeId);

    /**
     * Determine whether a Node's information has been received.  For
     * sleeping nodes, this may take a while (until the node wakes).
     *
     * @param nodeId The node ID
     * @return true if the node information is known, false otherwise
     */
    bool isNodeInfoReceived(int nodeId);

    /**
     * Determine if the Z-Wave network has been initialized yet.
     *
     * @return true if the network is initialized, false otherwise
     */
    bool isInitialized()
    {
      return m_initialized;
    }

  protected:
    /**
     * OZW constructor
     */
    OZW();

    /**
     * OZW Destructor
     */
    ~OZW();

    /**
     * Based on a nodeId and a value index, lookup the corresponding
     * OpenZWave ValueID.
     *
     * @param nodeId The node ID
     * @param index The value index (see dumpNodes()) of the value to query.
     * @param A pointer to a ValueID that will be returned if successful
     * @return true of the nodeId and index was found, false otherwise
     */
    bool getValueID(int nodeId, int index, OpenZWave::ValueID *vid);

    /**
     * Return the Home ID of the network.
     *
     * @return The Home ID.
     */
    uint32_t getHomeID()
    {
      return m_homeId;
    }

    /**
     * Lock the m_zwNodeMap mutex to protect against changes made to
     * the the the map by the OpenZWave notification handler.  Always
     * lock this mutex when acessing anything in the zwNodeMap map.
     */
    void lockNodes() { pthread_mutex_lock(&m_nodeLock); };

    /**
     * Unlock the m_zwNodeMap mutex after lockNodes() has been called.
     */
    void unlockNodes() { pthread_mutex_unlock(&m_nodeLock); };

  private:
    // prevent copying and assignment
    OZW(OZW const &) = delete;
    OZW& operator=(OZW const&) = delete;

    // our class instance
    static OZW* m_instance;

    uint32_t m_homeId;
    bool m_mgrCreated;
    bool m_driverFailed;
    bool m_debugging;
    bool m_initialized;

    bool m_driverIsHID;
    std::string m_devicePath;

    // our notification handler, called by OpenZWave for events on the
    // network.
    static void notificationHandler(OpenZWave::Notification 
                                    const* notification, 
                                    void *ctx);

    // a map of added nodes
    zwNodeMap_t m_zwNodeMap;

    // for coordinating access to the node list
    pthread_mutex_t m_nodeLock;

    // We use these to determine init failure or success (if OpenZWave
    // has successfully queried essential data about the network).
    pthread_mutex_t m_initLock;
    pthread_cond_t m_initCond;
  };
}


