//-----------------------------------------------------------------------------
//
//	DoorLock.cpp
//
//	Implementation of the Z-Wave COMMAND_CLASS_DOOR_LOCK
//
//	Copyright (c) 2014 Justin Hammond <justin@dynam.ac>
//
//	SOFTWARE NOTICE AND LICENSE
//
//	This file is part of OpenZWave.
//
//	OpenZWave 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 3 of the License,
//	or (at your option) any later version.
//
//	OpenZWave 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 OpenZWave.  If not, see <http://www.gnu.org/licenses/>.
//
//-----------------------------------------------------------------------------

#include "command_classes/CommandClasses.h"
#include "command_classes/DoorLock.h"
#include "command_classes/WakeUp.h"
#include "Defs.h"
#include "Msg.h"
#include "Node.h"
#include "Driver.h"
#include "platform/Log.h"

#include "value_classes/ValueBool.h"
#include "value_classes/ValueByte.h"
#include "value_classes/ValueInt.h"

#include "tinyxml.h"

namespace OpenZWave
{
	namespace Internal
	{
		namespace CC
		{

			enum DoorLockCmd
			{
				DoorLockCmd_Set = 0x01,
				DoorLockCmd_Get = 0x02,
				DoorLockCmd_Report = 0x03,
				DoorLockCmd_Configuration_Set = 0x04,
				DoorLockCmd_Configuration_Get = 0x05,
				DoorLockCmd_Configuration_Report = 0x06
			};

			enum TimeOutMode
			{
				DoorLockConfig_NoTimeout = 0x01,
				DoorLockConfig_Timeout = 0x02
			};

			static char const* c_TimeOutModeNames[] =
			{ "No Timeout", "Secure Lock after Timeout" };

			enum DoorLockControlState
			{
				DoorLockControlState_Handle1 = 0x01,
				DoorLockControlState_Handle2 = 0x02,
				DoorLockControlState_Handle3 = 0x04,
				DoorLockControlState_Handle4 = 0x08
			};

			enum DoorLockState
			{
				DoorLockState_Unsecured = 0x00,
				DoorLockState_Unsecured_Timeout = 0x01,
				DoorLockState_Inside_Unsecured = 0x10,
				DoorLockState_Inside_Unsecured_Timeout = 0x11,
				DoorLockState_Outside_Unsecured = 0x20,
				DoorLockState_Outside_Unsecured_Timeout = 0x21,
				DoorLockState_Secured = 0xFF
			};

			static char const* c_LockStateNames[] =
			{ "Unsecure", "Unsecured with Timeout", "Inside Handle Unsecured", "Inside Handle Unsecured with Timeout", "Outside Handle Unsecured", "Outside Handle Unsecured with Timeout", "Secured", "Invalid" };

//-----------------------------------------------------------------------------
// <DoorLock::DoorLock>
// Constructor
//-----------------------------------------------------------------------------
			DoorLock::DoorLock(uint32 const _homeId, uint8 const _nodeId) :
					CommandClass(_homeId, _nodeId)
			{
				m_dom.EnableFlag(STATE_FLAG_DOORLOCK_TIMEOUT, 0);
				m_dom.EnableFlag(STATE_FLAG_DOORLOCK_INSIDEMODE, 0);
				m_dom.EnableFlag(STATE_FLAG_DOORLOCK_OUTSIDEMODE, 0);
				m_dom.EnableFlag(STATE_FLAG_DOORLOCK_TIMEOUTMINS, 0xFE);
				m_dom.EnableFlag(STATE_FLAG_DOORLOCK_TIMEOUTSECS, 0xFE);
				SetStaticRequest(StaticRequest_Values);
			}

//-----------------------------------------------------------------------------
// <DoorLock::RequestState>
// Request current state from the device
//-----------------------------------------------------------------------------
			bool DoorLock::RequestState(uint32 const _requestFlags, uint8 const _instance, Driver::MsgQueue const _queue)
			{
				bool requests = false;
				if ((_requestFlags & RequestFlag_Static) && HasStaticRequest(StaticRequest_Values))
				{
					requests = RequestValue(_requestFlags, ValueID_Index_DoorLock::System_Config_Mode, _instance, _queue);
				}

				if (_requestFlags & RequestFlag_Dynamic)
				{
					requests |= RequestValue(_requestFlags, ValueID_Index_DoorLock::Lock, _instance, _queue);
				}

				return requests;
			}

//-----------------------------------------------------------------------------
// <DoorLock::RequestValue>
// Request current value from the device
//-----------------------------------------------------------------------------
			bool DoorLock::RequestValue(uint32 const _requestFlags, uint16 const _what, uint8 const _instance, Driver::MsgQueue const _queue)
			{
				if (_what >= ValueID_Index_DoorLock::System_Config_Mode)
				{
					Msg* msg = new Msg("DoorLockCmd_Configuration_Get", GetNodeId(), REQUEST, FUNC_ID_ZW_SEND_DATA, true, true, FUNC_ID_APPLICATION_COMMAND_HANDLER, GetCommandClassId());
					msg->SetInstance(this, _instance);
					msg->Append(GetNodeId());
					msg->Append(2);
					msg->Append(GetCommandClassId());
					msg->Append(DoorLockCmd_Configuration_Get);
					msg->Append(GetDriver()->GetTransmitOptions());
					GetDriver()->SendMsg(msg, _queue);
					return true;

				}
				else if ((_what == ValueID_Index_DoorLock::Lock) || (_what == ValueID_Index_DoorLock::Lock_Mode))
				{

					if (m_com.GetFlagBool(COMPAT_FLAG_GETSUPPORTED))
					{
						Msg* msg = new Msg("DoorLockCmd_Get", GetNodeId(), REQUEST, FUNC_ID_ZW_SEND_DATA, true, true, FUNC_ID_APPLICATION_COMMAND_HANDLER, GetCommandClassId());
						msg->SetInstance(this, _instance);
						msg->Append(GetNodeId());
						msg->Append(2);
						msg->Append(GetCommandClassId());
						msg->Append(DoorLockCmd_Get);
						msg->Append(GetDriver()->GetTransmitOptions());
						GetDriver()->SendMsg(msg, _queue);
						return true;
					}
					else
					{
						Log::Write(LogLevel_Info, GetNodeId(), "DoorLockCmd_Get Not Supported on this node");
					}
				}
				return false;
			}

//-----------------------------------------------------------------------------
// <DoorLock::HandleMsg>
// Handle a message from the Z-Wave network
//-----------------------------------------------------------------------------
			bool DoorLock::HandleMsg(uint8 const* _data, uint32 const _length, uint32 const _instance	// = 1
					)
			{

				if (DoorLockCmd_Report == (DoorLockCmd) _data[0])
				{
					uint8 lockState = (_data[1] == 0xFF) ? 6 : _data[1];
					if (lockState > 6) /* size of c_LockStateNames minus Invalid Entry */
					{
						Log::Write(LogLevel_Warning, GetNodeId(), "LockState Value was greater than range. Setting to Invalid");
						lockState = 7;
					}

					Log::Write(LogLevel_Info, GetNodeId(), "Received DoorLock report: DoorLock is %s", c_LockStateNames[lockState]);

					if (Internal::VC::ValueBool* value = static_cast<Internal::VC::ValueBool*>(GetValue(_instance, ValueID_Index_DoorLock::Lock)))
					{
						value->OnValueRefreshed(lockState == 0x06);
						value->Release();
					}
					if (Internal::VC::ValueList* value = static_cast<Internal::VC::ValueList*>(GetValue(_instance, ValueID_Index_DoorLock::Lock_Mode)))
					{
						value->OnValueRefreshed(lockState);
						value->Release();
					}
					return true;
				}
				else if (DoorLockCmd_Configuration_Report == (DoorLockCmd) _data[0])
				{
					switch (_data[1])
					{
						case DoorLockConfig_NoTimeout:
							m_dom.SetFlagByte(STATE_FLAG_DOORLOCK_TIMEOUT, DoorLockConfig_NoTimeout);
							RemoveValue(_instance, ValueID_Index_DoorLock::System_Config_Minutes);
							RemoveValue(_instance, ValueID_Index_DoorLock::System_Config_Seconds);
							m_dom.SetFlagByte(STATE_FLAG_DOORLOCK_TIMEOUTMINS, 0xFE);
							m_dom.SetFlagByte(STATE_FLAG_DOORLOCK_TIMEOUTSECS, 0xFE);
							break;
						case DoorLockConfig_Timeout:
							/* if we have a timeout, then create the Values for the timeout config */
							if (Node* node = GetNodeUnsafe())
							{
								node->CreateValueInt(ValueID::ValueGenre_System, GetCommandClassId(), _instance, ValueID_Index_DoorLock::System_Config_Minutes, "Timeout Minutes", "Mins", false, false, _data[3], 0);
								node->CreateValueInt(ValueID::ValueGenre_System, GetCommandClassId(), _instance, ValueID_Index_DoorLock::System_Config_Seconds, "Timeout Seconds", "Secs", false, false, _data[4], 0);
							}
							m_dom.SetFlagByte(STATE_FLAG_DOORLOCK_TIMEOUT, DoorLockConfig_Timeout);
							m_dom.SetFlagByte(STATE_FLAG_DOORLOCK_TIMEOUTMINS, _data[3]);
							m_dom.SetFlagByte(STATE_FLAG_DOORLOCK_TIMEOUTSECS, _data[4]);
							break;
						default:
							Log::Write(LogLevel_Warning, GetNodeId(), "Received a Unsupported Door Lock Config Report %d", _data[1]);
					}

					if (Internal::VC::ValueByte* value = static_cast<Internal::VC::ValueByte*>(GetValue(_instance, ValueID_Index_DoorLock::System_Config_OutsideHandles)))
					{
						value->OnValueRefreshed(((_data[2] & 0xF0) >> 4));
						value->Release();
						m_dom.SetFlagByte(STATE_FLAG_DOORLOCK_OUTSIDEMODE, ((_data[2] & 0xF0) >> 4));
					}
					if (Internal::VC::ValueByte* value = static_cast<Internal::VC::ValueByte*>(GetValue(_instance, ValueID_Index_DoorLock::System_Config_InsideHandles)))
					{
						value->OnValueRefreshed((_data[2] & 0x0F));
						value->Release();
						m_dom.SetFlagByte(STATE_FLAG_DOORLOCK_INSIDEMODE, (_data[2] & 0x0F));
					}

					ClearStaticRequest(StaticRequest_Values);
					Log::Write(LogLevel_Info, GetNodeId(), "REcieved DoorLock Config Report: OutsideMode %d, InsideMode %d, Timeout Enabled: %d : %d:%d", ((_data[2] & 0xF0) >> 4), (_data[2] & 0x0F), _data[1], _data[3], _data[4]);
					return true;
				}
				return false;
			}

//-----------------------------------------------------------------------------
// <DoorLock::SetValue>
// Set the lock's state
//-----------------------------------------------------------------------------
			bool DoorLock::SetValue(Internal::VC::Value const& _value)
			{
				uint8 instance = _value.GetID().GetInstance();
				if ((ValueID_Index_DoorLock::Lock == _value.GetID().GetIndex()) && ValueID::ValueType_Bool == _value.GetID().GetType())
				{
					Internal::VC::ValueBool const* value = static_cast<Internal::VC::ValueBool const*>(&_value);

					Log::Write(LogLevel_Info, GetNodeId(), "ValueID_Index_DoorLock::Lock::Set - Requesting lock to be %s", value->GetValue() ? "Locked" : "Unlocked");
					Msg* msg = new Msg("DoorLockCmd_Set", GetNodeId(), REQUEST, FUNC_ID_ZW_SEND_DATA, true, true, FUNC_ID_APPLICATION_COMMAND_HANDLER, GetCommandClassId());
					msg->SetInstance(this, _value.GetID().GetInstance());
					msg->Append(GetNodeId());
					msg->Append(3);
					msg->Append(GetCommandClassId());
					msg->Append(DoorLockCmd_Set);
					msg->Append(value->GetValue() ? 0xFF : 0x00);
					msg->Append(GetDriver()->GetTransmitOptions());
					GetDriver()->SendMsg(msg, Driver::MsgQueue_Send);
					return true;
				}
				if ((ValueID_Index_DoorLock::Lock_Mode == _value.GetID().GetIndex()) && (ValueID::ValueType_List == _value.GetID().GetType()))
				{
					Internal::VC::ValueList const* value = static_cast<Internal::VC::ValueList const*>(&_value);
					Internal::VC::ValueList::Item const *item = value->GetItem();
					if (item == NULL)
						return false;

					Log::Write(LogLevel_Info, GetNodeId(), "ValueID_Index_DoorLock::Lock_Mode::Set - Requesting lock to be %s", item->m_label.c_str());
					Msg* msg = new Msg("DoorLockCmd_Set", GetNodeId(), REQUEST, FUNC_ID_ZW_SEND_DATA, true, true, FUNC_ID_APPLICATION_COMMAND_HANDLER, GetCommandClassId());
					msg->SetInstance(this, _value.GetID().GetInstance());
					msg->Append(GetNodeId());
					msg->Append(3);
					msg->Append(GetCommandClassId());
					msg->Append(DoorLockCmd_Set);
					msg->Append(item->m_value);
					msg->Append(GetDriver()->GetTransmitOptions());
					GetDriver()->SendMsg(msg, Driver::MsgQueue_Send);
					return true;
				}
				/* if its any of our System Messages.... */
				if (ValueID_Index_DoorLock::System_Config_Mode <= _value.GetID().GetIndex() && ValueID_Index_DoorLock::System_Config_InsideHandles >= _value.GetID().GetIndex())
				{
					bool sendmsg = true;
					switch (_value.GetID().GetIndex())
					{
						/* this is a List */
						case ValueID_Index_DoorLock::System_Config_Mode:
							if (ValueID::ValueType_List != _value.GetID().GetType())
							{
								sendmsg = false;
								break;
							}
							if (Internal::VC::ValueList* value = static_cast<Internal::VC::ValueList*>(GetValue(instance, _value.GetID().GetIndex())))
							{
								Internal::VC::ValueList::Item const *item = (static_cast<Internal::VC::ValueList const*>(&_value))->GetItem();
								if (item != NULL)
									value->OnValueRefreshed(item->m_value);
								value->Release();
							}

							break;

							/* these are a int */
						case ValueID_Index_DoorLock::System_Config_Minutes:
						case ValueID_Index_DoorLock::System_Config_Seconds:
							if (ValueID::ValueType_Int != _value.GetID().GetType())
							{
								sendmsg = false;
								break;
							}
							if (Internal::VC::ValueInt* value = static_cast<Internal::VC::ValueInt*>(GetValue(instance, _value.GetID().GetIndex())))
							{
								value->OnValueRefreshed((static_cast<Internal::VC::ValueInt const*>(&_value))->GetValue());
								value->Release();
							}
							break;
							/* these are a byte */
						case ValueID_Index_DoorLock::System_Config_OutsideHandles:
						case ValueID_Index_DoorLock::System_Config_InsideHandles:
							if (ValueID::ValueType_Byte != _value.GetID().GetType())
							{
								sendmsg = false;
								break;
							}
							if (Internal::VC::ValueByte* value = static_cast<Internal::VC::ValueByte*>(GetValue(instance, _value.GetID().GetIndex())))
							{
								value->OnValueRefreshed((static_cast<Internal::VC::ValueByte const*>(&_value))->GetValue());
								value->Release();
							}
							break;
						default:
							Log::Write(LogLevel_Warning, GetNodeId(), "DoorLock::SetValue - Unhandled System_Config Variable %d", _value.GetID().GetIndex());
							sendmsg = false;
							break;
					}

					if (sendmsg)
					{
						bool ok = true;
						if (Internal::VC::ValueList* value = static_cast<Internal::VC::ValueList*>(GetValue(instance, ValueID_Index_DoorLock::System_Config_Mode)))
						{
							Internal::VC::ValueList::Item const *item = value->GetItem();
							if (item != NULL)
								m_dom.SetFlagByte(STATE_FLAG_DOORLOCK_TIMEOUT, item->m_value);
						}
						else
						{
							ok = false;
							Log::Write(LogLevel_Warning, GetNodeId(), "Failed To Retrieve ValueID_Index_DoorLock::System_Config_Mode For SetValue");
						}
						uint8 control = 0;
						if (Internal::VC::ValueByte* value = static_cast<Internal::VC::ValueByte*>(GetValue(instance, ValueID_Index_DoorLock::System_Config_OutsideHandles)))
						{
							control = (value->GetValue() << 4);
							m_dom.SetFlagByte(STATE_FLAG_DOORLOCK_INSIDEMODE, control);
						}
						else
						{
							ok = false;
							Log::Write(LogLevel_Warning, GetNodeId(), "Failed To Retrieve ValueID_Index_DoorLock::System_Config_OutsideHandles For SetValue");
						}
						if (Internal::VC::ValueByte* value = static_cast<Internal::VC::ValueByte*>(GetValue(instance, ValueID_Index_DoorLock::System_Config_InsideHandles)))
						{
							control += (value->GetValue() & 0x0F);
							m_dom.SetFlagByte(STATE_FLAG_DOORLOCK_OUTSIDEMODE, (value->GetValue() & 0x0F));
						}
						else
						{
							ok = false;
							Log::Write(LogLevel_Warning, GetNodeId(), "Failed To Retrieve ValueID_Index_DoorLock::System_Config_InsideHandles For SetValue");
						}
						if (Internal::VC::ValueInt* value = static_cast<Internal::VC::ValueInt*>(GetValue(instance, ValueID_Index_DoorLock::System_Config_Minutes)))
						{
							m_dom.SetFlagByte(STATE_FLAG_DOORLOCK_TIMEOUTMINS, value->GetValue());
						}
						else
						{
							/* Minutes and Seconds Might Not Exist, this is fine. Set to 0xFE */
							m_dom.SetFlagByte(STATE_FLAG_DOORLOCK_TIMEOUTMINS, 0xFE);
						}
						if (Internal::VC::ValueInt* value = static_cast<Internal::VC::ValueInt*>(GetValue(instance, ValueID_Index_DoorLock::System_Config_Seconds)))
						{
							m_dom.SetFlagByte(STATE_FLAG_DOORLOCK_TIMEOUTSECS, value->GetValue());
						}
						else
						{
							/* Minutes and Seconds Might Not Exist, this is fine. Set to 0xFE */
							m_dom.SetFlagByte(STATE_FLAG_DOORLOCK_TIMEOUTSECS, 0xFE);
						}
						if (ok)
						{
							Msg* msg = new Msg("DoorLockCmd_Configuration_Set", GetNodeId(), REQUEST, FUNC_ID_ZW_SEND_DATA, true, true, FUNC_ID_APPLICATION_COMMAND_HANDLER, GetCommandClassId());
							msg->SetInstance(this, _value.GetID().GetInstance());
							msg->Append(GetNodeId());
							msg->Append(6);
							msg->Append(GetCommandClassId());
							msg->Append(DoorLockCmd_Configuration_Set);
							msg->Append(m_dom.GetFlagByte(STATE_FLAG_DOORLOCK_TIMEOUT));
							msg->Append(control);
							msg->Append(m_dom.GetFlagByte(STATE_FLAG_DOORLOCK_TIMEOUTMINS));
							msg->Append(m_dom.GetFlagByte(STATE_FLAG_DOORLOCK_TIMEOUTSECS));
							msg->Append(GetDriver()->GetTransmitOptions());
							GetDriver()->SendMsg(msg, Driver::MsgQueue_Send);
							return true;
						}
						return false;
					}

				}
				return false;
			}

//-----------------------------------------------------------------------------
// <DoorLock::SetValueBasic>
// Update class values based in BASIC mapping
//-----------------------------------------------------------------------------
			void DoorLock::SetValueBasic(uint8 const _instance, uint8 const _value)
			{

				// Send a request for new value to synchronize it with the BASIC set/report.
				// In case the device is sleeping, we set the value anyway so the BASIC set/report
				// stays in sync with it. We must be careful mapping the uint8 BASIC value
				// into a class specific value.
				// When the device wakes up, the real requested value will be retrieved.
				RequestValue(0, DoorLockCmd_Get, _instance, Driver::MsgQueue_Send);
				if (Node* node = GetNodeUnsafe())
				{
					if (WakeUp* wakeUp = static_cast<WakeUp*>(node->GetCommandClass(WakeUp::StaticGetCommandClassId())))
					{
						if (!wakeUp->IsAwake())
						{
							if (Internal::VC::ValueBool* value = static_cast<Internal::VC::ValueBool*>(GetValue(_instance, ValueID_Index_DoorLock::Lock)))
							{
								value->OnValueRefreshed(_value != 0);
								value->Release();
							}
						}
					}
				}
			}

//-----------------------------------------------------------------------------
// <DoorLock::CreateVars>
// Create the values managed by this command class
//-----------------------------------------------------------------------------
			void DoorLock::CreateVars(uint8 const _instance)
			{
				if (Node* node = GetNodeUnsafe())
				{
					node->CreateValueBool(ValueID::ValueGenre_User, GetCommandClassId(), _instance, ValueID_Index_DoorLock::Lock, "Locked", "", false, false, false, 0);

					/* Complex Lock Option */
					{
						vector<Internal::VC::ValueList::Item> items;
						Internal::VC::ValueList::Item item;
						for (uint8 i = 0; i < 8; ++i)
						{
							item.m_label = c_LockStateNames[i];
							item.m_value = (i <= 6) ? i : 0xFF;
							items.push_back(item);
						}
						node->CreateValueList(ValueID::ValueGenre_User, GetCommandClassId(), _instance, ValueID_Index_DoorLock::Lock_Mode, "Locked (Advanced)", "", false, false, 1, items, 0, 0);

					}

					/* Timeout mode for Locks that support it */
					{
						vector<Internal::VC::ValueList::Item> items;
						Internal::VC::ValueList::Item item;
						for (uint8 i = 0; i < 2; ++i)
						{
							item.m_label = c_TimeOutModeNames[i];
							item.m_value = i + 1;
							items.push_back(item);
						}
						node->CreateValueList(ValueID::ValueGenre_System, GetCommandClassId(), _instance, ValueID_Index_DoorLock::System_Config_Mode, "Timeout Mode", "", false, false, 1, items, 0, 0);
					}
					node->CreateValueByte(ValueID::ValueGenre_System, GetCommandClassId(), _instance, ValueID_Index_DoorLock::System_Config_OutsideHandles, "Outside Handle Control", "", false, false, 0x0F, 0);
					node->CreateValueByte(ValueID::ValueGenre_System, GetCommandClassId(), _instance, ValueID_Index_DoorLock::System_Config_InsideHandles, "Inside Handle Control", "", false, false, 0x0F, 0);

				}
			}
		} // namespace CC
	} // namespace Internal
} // namespace OpenZWave

