/*
 * Author: Jon Trulson <jtrulson@ics.com>
 * Copyright (c) 2015 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.
 */

#include <unistd.h>
#include <iostream>
#include <stdexcept>
#include <string>
#include <pthread.h>

#include "platform/Log.h"

#include "zwNode.hpp"

#include "ozw.hpp"

using namespace upm;
using namespace std;
using namespace OpenZWave;


OZW::OZW()
{
  m_mgrCreated = false;
  m_driverFailed = false;
  m_homeId = 0;

  pthread_mutexattr_t mutexAttrib;
  pthread_mutexattr_init(&mutexAttrib);
  pthread_mutexattr_settype(&mutexAttrib, PTHREAD_MUTEX_RECURSIVE);

  if (pthread_mutex_init(&m_nodeLock, &mutexAttrib))
    {
      throw std::runtime_error(std::string(__FUNCTION__) +
                               ": pthread_mutex_init(nodeLock) failed");
    }

  pthread_mutexattr_destroy(&mutexAttrib);

  if (pthread_mutex_init(&m_initLock, NULL))
    {
      throw std::runtime_error(std::string(__FUNCTION__) +
                               ": pthread_mutex_init(initLock) failed");
    }

  // initialize our init conditional
  if (pthread_cond_init(&m_initCond, NULL))
    {
      throw std::runtime_error(std::string(__FUNCTION__) +
                               ": pthread_cond_init() failed");
    }

  setDebug(false);
}

OZW::~OZW()
{
  if (m_mgrCreated)
    {
      // remove the driver
      if (m_driverIsHID)
        Manager::Get()->RemoveDriver("HID");
      else
        Manager::Get()->RemoveDriver(m_devicePath);
        
      // remove the notification handler
      Manager::Get()->RemoveWatcher(notificationHandler, this);

      // now destroy Manager and Options.  Options must be destroyed
      // after the Manager is destroyed.
      Manager::Destroy();
      Options::Destroy();
    }

  pthread_mutex_destroy(&m_nodeLock);
  pthread_mutex_destroy(&m_initLock);
  pthread_cond_destroy(&m_initCond);

  // delete any nodes.  This should be safe after deleting the node
  // mutex since the handler is no longer registered.
  for (zwNodeMap_t::iterator it = m_zwNodeMap.begin();
       it != m_zwNodeMap.end(); ++it)
    {
      // delete the zwNode pointer
      delete (*it).second;
    }
  // empty the map
  m_zwNodeMap.clear();
}

void OZW::optionsCreate(std::string configPath, 
                        std::string userConfigDir,
                        std::string cmdLine)
{
  Options::Create(configPath, userConfigDir, cmdLine);
}

void OZW::optionAddInt(std::string name, int val)
{
  Options::Get()->AddOptionInt(name, val);
}

void OZW::optionAddBool(std::string name, bool val)
{
  Options::Get()->AddOptionBool(name, val);
}

void OZW::optionAddString(std::string name, std::string val, bool append)
{
  Options::Get()->AddOptionString(name, val, append);
}

void OZW::optionsLock()
{
  // lock the options if not already locked
  if (!Options::Get()->AreLocked())
    Options::Get()->Lock();
}

bool OZW::init(string devicePath, bool isHID)
{
  // make sure options are locked
  optionsLock();

  pthread_mutex_lock(&m_initLock);

  // the fun begins
  Manager::Create();

  // add our event handler
  Manager::Get()->AddWatcher(notificationHandler, this);

  // now add the driver
  m_devicePath = devicePath;
  if (isHID)
    {
      m_driverIsHID = true;
      Manager::Get()->AddDriver("HID",
                                Driver::ControllerInterface_Hid);
    }
  else
    Manager::Get()->AddDriver(devicePath);

  m_mgrCreated = true;

  // now we block here waiting for the driver to get far enough along
  // (or fail) to proceed further
  pthread_cond_wait(&m_initCond, &m_initLock);

  if (m_driverFailed)
    {
      throw std::runtime_error(std::string(__FUNCTION__) +
                               ": driver initialization failed");
      return false;
    }

  return true;
}

void OZW::notificationHandler(Notification const* notification, void *ctx)
{
  upm::OZW *This = (upm::OZW *)ctx;
  
  This->lockNodes();

  if (This->m_debugging)
    fprintf(stderr, "### %s: homeId %08x, nodeId %d, type %x\n",
            __FUNCTION__,
            notification->GetHomeId(),
            notification->GetNodeId(),
            notification->GetType());
  
  const uint32_t homeId = notification->GetHomeId();
  const uint8_t nodeId = notification->GetNodeId();

  switch (notification->GetType()) 
    {

    case Notification::Type_NodeAdded:
    case Notification::Type_NodeNew:
      {
        if (This->m_debugging)
          cerr << "### ### ADDING NODE: " << int(nodeId) << endl;
        zwNode *node = new zwNode(homeId, nodeId);
        This->m_zwNodeMap.insert(std::pair<uint8_t, zwNode *>(nodeId, node));

        break;
      }

    case Notification::Type_NodeRemoved:
      { 
        if (This->m_debugging)
          cerr << "### ### REMOVING NODE: " << int(nodeId) << endl;
        if (This->m_zwNodeMap.count(nodeId) != 0)
          {
            delete This->m_zwNodeMap[nodeId];
            This->m_zwNodeMap.erase(nodeId);
          }

        break;
      }

    case Notification::Type_ValueAdded:
      {
        if (This->m_debugging)
          cerr << "### ### VALUE ADDED " << endl;
        This->m_zwNodeMap[nodeId]->addValueID(notification->GetValueID());

        break;
      }
      
    case Notification::Type_ValueRemoved:
      {
        if (This->m_debugging)
          cerr << "### ### VALUE DELETED " << endl;
        This->m_zwNodeMap[nodeId]->removeValueID(notification->GetValueID());

        break;
      }

    case Notification::Type_ValueChanged:
      {
        // might be able to do something with this someday...
        break;
      }

    case Notification::Type_DriverReset:
      {
        // all nodes deleted.  According to OZW docs, this happens
        // when a driver is reset, instead of sending potentially
        // hundreds of ValueRemoved/NodeRemoved events.
        for (zwNodeMap_t::iterator it = This->m_zwNodeMap.begin();
             it != This->m_zwNodeMap.end(); ++it)
          {
            // delete the zwNode pointer
            delete (*it).second;
          }
        // empty the map
        This->m_zwNodeMap.clear();

        break;
      }

    case Notification::Type_DriverReady:
      {
        if (This->m_debugging)
          fprintf(stderr, "### DriverReady, homeID = %08x\n", This->m_homeId);
        This->m_homeId = notification->GetHomeId();
        break;
      }

    case Notification::Type_DriverFailed:
      {
        if (This->m_debugging)
          cerr << "### Driver FAILED" << endl;
        This->m_driverFailed = true;
        // wake up init()
        pthread_cond_broadcast(&(This->m_initCond));
        break;
      }

    case Notification::Type_AwakeNodesQueried:
    case Notification::Type_AllNodesQueried:
    case Notification::Type_AllNodesQueriedSomeDead:
      {
        if (This->m_debugging)
          cerr << "### Awake/All/SomeDead complete" << endl;
        // wake up init()
        pthread_cond_broadcast(&(This->m_initCond));
        break;
      }

      // might be able to do something with these someday too
    case Notification::Type_Notification:
    case Notification::Type_NodeNaming:
    case Notification::Type_NodeProtocolInfo:
    case Notification::Type_NodeQueriesComplete:
    case Notification::Type_PollingEnabled:
    case Notification::Type_PollingDisabled:
    case Notification::Type_NodeEvent:
    case Notification::Type_Group:
    default:
      {
        break;
      }
    }

  This->unlockNodes();
}

void OZW::dumpNodes(bool all)
{
  // iterate through all the nodes and dump various info on them

  lockNodes();

  for (zwNodeMap_t::iterator it = m_zwNodeMap.begin();
       it != m_zwNodeMap.end(); ++it)
    {
      uint8_t nodeId = (*it).first;

      cerr << "Node " << int(nodeId)
           << ": "
           << Manager::Get()->GetNodeProductName(m_homeId, nodeId)
           << endl;
      (*it).second->dumpNode(all);
    }

  unlockNodes();
}

bool OZW::getValueID(int nodeId, int index, ValueID *vid)
{
  // nodeId's are uint8_t's in OpenZWave, but we want to use int's to
  // avoid hassles when dealing with SWIG, so here we just throw away
  // everything except the first byte.
  nodeId &= 0xff;

  lockNodes();

  zwNodeMap_t::iterator it;

  it = m_zwNodeMap.find(nodeId);

  if (it == m_zwNodeMap.end())
    {
      cerr << __FUNCTION__ << ": Node " << nodeId 
           << " does not exist" << endl;
      unlockNodes();
      return false;
    }

  // now get the ValueID
  if (!(*it).second->indexToValueID(index, vid))
    {
      cerr << __FUNCTION__ << ": Index " << index 
           << " for node " << nodeId 
           << " does not exist" << endl;
      unlockNodes();
      return false;
    }

  unlockNodes();
  return true;
}

string OZW::getValueAsString(int nodeId, int index)
{
  // we have to play this game since there is no default ctor for ValueID
  ValueID vid(m_homeId, (uint64)0);

  string rv;

  lockNodes();

  if (getValueID(nodeId, index, &vid))
    Manager::Get()->GetValueAsString(vid, &rv);

  unlockNodes();

  return rv;
}

string OZW::getValueUnits(int nodeId, int index)
{
  ValueID vid(m_homeId, (uint64)0);

  lockNodes();

  string rv;
  if (getValueID(nodeId, index, &vid))
    rv = Manager::Get()->GetValueUnits(vid);

  unlockNodes();

  return rv;
}

void OZW::setValueUnits(int nodeId, int index, string text)
{
  ValueID vid(m_homeId, (uint64)0);

  lockNodes();

  if (getValueID(nodeId, index, &vid))
    Manager::Get()->SetValueUnits(vid, text);

  unlockNodes();
}

string OZW::getValueLabel(int nodeId, int index)
{
  ValueID vid(m_homeId, (uint64)0);

  lockNodes();

  string rv;
  if (getValueID(nodeId, index, &vid))
    rv = Manager::Get()->GetValueLabel(vid);

  unlockNodes();

  return rv;
}

void OZW::setValueLabel(int nodeId, int index, string text)
{
  ValueID vid(m_homeId, (uint64)0);

  lockNodes();

  if (getValueID(nodeId, index, &vid))
    Manager::Get()->SetValueLabel(vid, text);

  unlockNodes();
}

string OZW::getValueHelp(int nodeId, int index)
{
  ValueID vid(m_homeId, (uint64)0);

  lockNodes();

  string rv;
  if (getValueID(nodeId, index, &vid))
    rv = Manager::Get()->GetValueHelp(vid);

  unlockNodes();

  return rv;
}

void OZW::setValueHelp(int nodeId, int index, string text)
{
  ValueID vid(m_homeId, (uint64)0);

  lockNodes();

  if (getValueID(nodeId, index, &vid))
    Manager::Get()->SetValueHelp(vid, text);

  unlockNodes();
}

void OZW::setValueAsBool(int nodeId, int index, bool val)
{
  if (isValueReadOnly(nodeId, index))
    {
      cerr << __FUNCTION__ << ": Node " << nodeId << " index " << index
           << " is ReadOnly" << endl;
      return;
    }

  ValueID vid(m_homeId, (uint64)0);

  lockNodes();

  if (getValueID(nodeId, index, &vid))
    {
      if (!Manager::Get()->SetValue(vid, val))
        {
          cerr << __FUNCTION__ << ": Value is not a bool type" << endl;
        }
    }

  unlockNodes();
}

void OZW::setValueAsByte(int nodeId, int index, uint8_t val)
{
  if (isValueReadOnly(nodeId, index))
    {
      cerr << __FUNCTION__ << ": Node " << nodeId << " index " << index
           << " is ReadOnly" << endl;
      return;
    }

  ValueID vid(m_homeId, (uint64)0);

  lockNodes();

  if (getValueID(nodeId, index, &vid))
    {
      if (!Manager::Get()->SetValue(vid, val))
        {
          cerr << __FUNCTION__ << ": Value is not a byte type" << endl;
        }
    }

  unlockNodes();
}

void OZW::setValueAsFloat(int nodeId, int index, float val)
{
  if (isValueReadOnly(nodeId, index))
    {
      cerr << __FUNCTION__ << ": Node " << nodeId << " index " << index
           << " is ReadOnly" << endl;
      return;
    }

  ValueID vid(m_homeId, (uint64)0);

  lockNodes();

  if (getValueID(nodeId, index, &vid))
    {
      if (!Manager::Get()->SetValue(vid, val))
        {
          cerr << __FUNCTION__ << ": Value is not a float type" << endl;
        }
    }

  unlockNodes();
}

void OZW::setValueAsInt32(int nodeId, int index, int32_t val)
{
  if (isValueReadOnly(nodeId, index))
    {
      cerr << __FUNCTION__ << ": Node " << nodeId << " index " << index
           << " is ReadOnly" << endl;
      return;
    }

  ValueID vid(m_homeId, (uint64)0);

  lockNodes();

  if (getValueID(nodeId, index, &vid))
    {
      if (!Manager::Get()->SetValue(vid, val))
        {
          cerr << __FUNCTION__ << ": Value is not a int32 type" << endl;
        }
    }

  unlockNodes();
}

void OZW::setValueAsInt16(int nodeId, int index, int16_t val)
{
  if (isValueReadOnly(nodeId, index))
    {
      cerr << __FUNCTION__ << ": Node " << nodeId << " index " << index
           << " is ReadOnly" << endl;
      return;
    }

  ValueID vid(m_homeId, (uint64)0);

  lockNodes();

  if (getValueID(nodeId, index, &vid))
    {
      if (!Manager::Get()->SetValue(vid, val))
        {
          cerr << __FUNCTION__ << ": Value is not a int16 type" << endl;
        }
    }

  unlockNodes();
}

void OZW::setValueAsBytes(int nodeId, int index, uint8_t *val, uint8_t len)
{
  if (isValueReadOnly(nodeId, index))
    {
      cerr << __FUNCTION__ << ": Node " << nodeId << " index " << index
           << " is ReadOnly" << endl;
      return;
    }

  ValueID vid(m_homeId, (uint64)0);

  lockNodes();

  if (getValueID(nodeId, index, &vid))
    {
      if (!Manager::Get()->SetValue(vid, val, len))
        {
          cerr << __FUNCTION__ << ": Value is not a bytes type" << endl;
        }
    }

  unlockNodes();
}

void OZW::setValueAsString(int nodeId, int index, string val)
{
  if (isValueReadOnly(nodeId, index))
    {
      cerr << __FUNCTION__ << ": Node " << nodeId << " index " << index
           << " is ReadOnly" << endl;
      return;
    }

  ValueID vid(m_homeId, (uint64)0);

  lockNodes();

  if (getValueID(nodeId, index, &vid))
    {
      if (!Manager::Get()->SetValue(vid, val))
        {
          // this should always succeed, but for consistancy...
          cerr << __FUNCTION__ << ": Value is not a string type" << endl;
        }
    }

  unlockNodes();
}

void OZW::refreshValue(int nodeId, int index)
{
  ValueID vid(m_homeId, (uint64)0);

  lockNodes();

  if (getValueID(nodeId, index, &vid))
    Manager::Get()->RefreshValue(vid);

  unlockNodes();
}

int OZW::getValueMin(int nodeId, int index)
{
  ValueID vid(m_homeId, (uint64)0);

  lockNodes();

  int rv = 0;
  if (getValueID(nodeId, index, &vid))
    rv = Manager::Get()->GetValueMin(vid);

  unlockNodes();
  return rv;
}

int OZW::getValueMax(int nodeId, int index)
{
  ValueID vid(m_homeId, (uint64)0);

  lockNodes();

  int rv = 0;
  if (getValueID(nodeId, index, &vid))
    rv = Manager::Get()->GetValueMax(vid);

  unlockNodes();
  return rv;
}

bool OZW::isValueReadOnly(int nodeId, int index)
{
  ValueID vid(m_homeId, (uint64)0);

  lockNodes();

  bool rv = false;
  if (getValueID(nodeId, index, &vid))
    rv = Manager::Get()->IsValueReadOnly(vid);

  unlockNodes();
  return rv;
}

bool OZW::isValueWriteOnly(int nodeId, int index)
{
  ValueID vid(m_homeId, (uint64)0);

  lockNodes();

  bool rv = false;
  if (getValueID(nodeId, index, &vid))
    rv = Manager::Get()->IsValueWriteOnly(vid);

  unlockNodes();
  return rv;
}

bool OZW::isValueSet(int nodeId, int index)
{
  ValueID vid(m_homeId, (uint64)0);

  lockNodes();

  bool rv = false;
  if (getValueID(nodeId, index, &vid))
    rv = Manager::Get()->IsValueSet(vid);

  unlockNodes();
  return rv;
}

bool OZW::isValuePolled(int nodeId, int index)
{
  ValueID vid(m_homeId, (uint64)0);

  lockNodes();

  bool rv = false;
  if (getValueID(nodeId, index, &vid))
    rv = Manager::Get()->IsValuePolled(vid);

  unlockNodes();
  return rv;
}

bool OZW::getValueAsBool(int nodeId, int index)
{
  if (isValueWriteOnly(nodeId, index))
    {
      cerr << __FUNCTION__ << ": Node " << nodeId << " index " << index
           << " is WriteOnly" << endl;
      return false;
    }

  ValueID vid(m_homeId, (uint64)0);

  lockNodes();

  bool rv = false;
  if (getValueID(nodeId, index, &vid))
    {
      if (!Manager::Get()->GetValueAsBool(vid, &rv))
        {
          cerr << __FUNCTION__ << ": Value is not a bool type, returning " 
               << rv << endl;
        }
    }

  unlockNodes();
  return rv;
}

uint8_t OZW::getValueAsByte(int nodeId, int index)
{
  if (isValueWriteOnly(nodeId, index))
    {
      cerr << __FUNCTION__ << ": Node " << nodeId << " index " << index
           << " is WriteOnly" << endl;
      return 0;
    }

  ValueID vid(m_homeId, (uint64)0);

  lockNodes();

  uint8_t rv = false;
  if (getValueID(nodeId, index, &vid))
    {
      if (!Manager::Get()->GetValueAsByte(vid, &rv))
        {
          cerr << __FUNCTION__ << ": Value is not a byte type, returning " 
               << int(rv) << endl;
        }
    }

  unlockNodes();
  return rv;
}

float OZW::getValueAsFloat(int nodeId, int index)
{
  if (isValueWriteOnly(nodeId, index))
    {
      cerr << __FUNCTION__ << ": Node " << nodeId << " index " << index
           << " is WriteOnly" << endl;
      return 0.0;
    }

  ValueID vid(m_homeId, (uint64)0);

  lockNodes();

  float rv = 0.0;
  if (getValueID(nodeId, index, &vid))
    {
      if (!Manager::Get()->GetValueAsFloat(vid, &rv))
        {
          cerr << __FUNCTION__ << ": Value is not a float type, returning " 
               << rv << endl;
        }
    }

  unlockNodes();
  return rv;
}

int OZW::getValueAsInt32(int nodeId, int index)
{
  if (isValueWriteOnly(nodeId, index))
    {
      cerr << __FUNCTION__ << ": Node " << nodeId << " index " << index
           << " is WriteOnly" << endl;
      return 0;
    }

  ValueID vid(m_homeId, (uint64)0);

  lockNodes();

  int32_t rv = 0;
  if (getValueID(nodeId, index, &vid))
    {
      if (!Manager::Get()->GetValueAsInt(vid, &rv))
        {
          cerr << __FUNCTION__ << ": Value is not an int32 type, returning " 
               << rv << endl;
        }
    }

  unlockNodes();
  return int(rv);
}

int OZW::getValueAsInt16(int nodeId, int index)
{
  if (isValueWriteOnly(nodeId, index))
    {
      cerr << __FUNCTION__ << ": Node " << nodeId << " index " << index
           << " is WriteOnly" << endl;
      return 0;
    }

  ValueID vid(m_homeId, (uint64)0);

  lockNodes();

  int16_t rv = 0;
  if (getValueID(nodeId, index, &vid))
    {
      if (!Manager::Get()->GetValueAsShort(vid, &rv))
        {
          cerr << __FUNCTION__ << ": Value is not an int16 type, returning " 
               << rv << endl;
        }
    }

  unlockNodes();
  return int(rv);
}

bool OZW::isNodeListeningDevice(int nodeId)
{
  nodeId &= 0xff;

  lockNodes();

  bool rv = Manager::Get()->IsNodeListeningDevice(m_homeId, nodeId);

  unlockNodes();
  return rv;
}

bool OZW::isNodeFrequentListeningDevice(int nodeId)
{
  nodeId &= 0xff;

  lockNodes();

  bool rv = Manager::Get()->IsNodeFrequentListeningDevice(m_homeId, nodeId);

  unlockNodes();
  return rv;
}

bool OZW::isNodeAwake(int nodeId)
{
  nodeId &= 0xff;

  lockNodes();

  bool rv = Manager::Get()->IsNodeAwake(m_homeId, nodeId);

  unlockNodes();
  return rv;
}

void OZW::setDebug(bool enable)
{ 
  m_debugging = enable; 

  // To bad the following does not seem to affect anything.  The only
  // way I've found to control it is via the options.xml file.

  //  Log::SetLoggingState(enable);
}
