// *******************************************************************
// * groups-server.c
// *
// *
// * Copyright 2010 by Ember Corporation. All rights reserved.              *80*
// *******************************************************************

#include "app/framework/include/af.h"

static bool isGroupPresent(uint8_t endpoint, uint16_t groupId);

static bool bindingGroupMatch(uint8_t endpoint,
                              uint16_t groupId,
                              EmberBindingTableEntry *entry);

static uint8_t findGroupIndex(uint8_t endpoint, uint16_t groupId);

void emberAfGroupsClusterServerInitCallback(uint8_t endpoint)
{
  // The high bit of Name Support indicates whether group names are supported.
  // Group names are not supported by this plugin.
  EmberAfStatus status;
  uint8_t nameSupport = (uint8_t) emberAfPluginGroupsServerGroupNamesSupportedCallback(endpoint);
  status = emberAfWriteAttribute(endpoint,
                                 ZCL_GROUPS_CLUSTER_ID,
                                 ZCL_GROUP_NAME_SUPPORT_ATTRIBUTE_ID,
                                 CLUSTER_MASK_SERVER,
                                 (uint8_t *)&nameSupport,
                                 ZCL_BITMAP8_ATTRIBUTE_TYPE);
  if (status != EMBER_ZCL_STATUS_SUCCESS) {
    emberAfGroupsClusterPrintln("ERR: writing name support %x", status);
  }
}

// --------------------------
// Internal functions used to maintain the group table within the context
// of the binding table.
//
// In the binding:
// The first two bytes of the identifier is set to the groupId
// The local endpoint is set to the endpoint that is mapped to this group
// --------------------------
static EmberAfStatus addEntryToGroupTable(uint8_t endpoint, uint16_t groupId, uint8_t *groupName)
{
  uint8_t i;

  // Check for duplicates.
  if (isGroupPresent(endpoint, groupId)) {
    // Even if the group already exists, tell the application about the name,
    // so it can cope with renames.
    emberAfPluginGroupsServerSetGroupNameCallback(endpoint,
                                                  groupId,
                                                  groupName);
    return EMBER_ZCL_STATUS_DUPLICATE_EXISTS;
  }

  // Look for an empty binding slot.
  for (i = 0; i < EMBER_BINDING_TABLE_SIZE; i++) {
    EmberBindingTableEntry binding;
    if (emberGetBinding(i, &binding) == EMBER_SUCCESS
        && binding.type == EMBER_UNUSED_BINDING) {
      EmberStatus status;
      binding.type = EMBER_MULTICAST_BINDING;
      binding.identifier[0] = LOW_BYTE(groupId);
      binding.identifier[1] = HIGH_BYTE(groupId);
      binding.local = endpoint;

      status = emberSetBinding(i, &binding);
      if (status == EMBER_SUCCESS) {
        // Set the group name, if supported
        emberAfPluginGroupsServerSetGroupNameCallback(endpoint,
                                                      groupId,
                                                      groupName);
        return EMBER_ZCL_STATUS_SUCCESS;
      } else {
        emberAfGroupsClusterPrintln("ERR: Failed to create binding (0x%x)",
                                    status);
      }
    }
  }
  emberAfGroupsClusterPrintln("ERR: Binding table is full");
  return EMBER_ZCL_STATUS_INSUFFICIENT_SPACE;
}

static EmberAfStatus removeEntryFromGroupTable(uint8_t endpoint, uint16_t groupId)
{
  if (isGroupPresent(endpoint, groupId)) {
    uint8_t bindingIndex = findGroupIndex(endpoint, groupId);
    EmberStatus status = emberDeleteBinding(bindingIndex);
    if (status == EMBER_SUCCESS) {
      uint8_t groupName[ZCL_GROUPS_CLUSTER_MAXIMUM_NAME_LENGTH + 1] = { 0 };
      emberAfPluginGroupsServerSetGroupNameCallback(endpoint,
                                                    groupId,
                                                    groupName);
      return EMBER_ZCL_STATUS_SUCCESS;
    } else {
      emberAfGroupsClusterPrintln("ERR: Failed to delete binding (0x%x)",
                                  status);
      return EMBER_ZCL_STATUS_FAILURE;
    }
  }
  return EMBER_ZCL_STATUS_NOT_FOUND;
}

bool emberAfGroupsClusterAddGroupCallback(uint16_t groupId, uint8_t *groupName)
{
  EmberAfStatus status;

  emberAfGroupsClusterPrint("RX: AddGroup 0x%2x, \"", groupId);
  emberAfGroupsClusterPrintString(groupName);
  emberAfGroupsClusterPrintln("\"");

  status = addEntryToGroupTable(emberAfCurrentEndpoint(), groupId, groupName);

  // For all networks, Add Group commands are only responded to when
  // they are addressed to a single device.
  if (emberAfCurrentCommand()->type != EMBER_INCOMING_UNICAST
      && emberAfCurrentCommand()->type != EMBER_INCOMING_UNICAST_REPLY) {
    return true;
  }
  emberAfFillCommandGroupsClusterAddGroupResponse(status, groupId);
  emberAfSendResponse();
  return true;
}

bool emberAfGroupsClusterViewGroupCallback(uint16_t groupId)
{
  EmberAfStatus status = EMBER_ZCL_STATUS_NOT_FOUND;
  EmberStatus sendStatus;
  uint8_t groupName[ZCL_GROUPS_CLUSTER_MAXIMUM_NAME_LENGTH + 1] = { 0 };

  // Get the group name, if supported
  emberAfPluginGroupsServerGetGroupNameCallback(emberAfCurrentEndpoint(),
                                                groupId,
                                                groupName);

  emberAfGroupsClusterPrintln("RX: ViewGroup 0x%2x", groupId);

  // For all networks, View Group commands can only be addressed to a
  // single device.
  if (emberAfCurrentCommand()->type != EMBER_INCOMING_UNICAST
      && emberAfCurrentCommand()->type != EMBER_INCOMING_UNICAST_REPLY) {
    return true;
  }

  if (isGroupPresent(emberAfCurrentEndpoint(), groupId)) {
    status = EMBER_ZCL_STATUS_SUCCESS;
  }

  emberAfFillCommandGroupsClusterViewGroupResponse(status, groupId, groupName);
  sendStatus = emberAfSendResponse();
  if (EMBER_SUCCESS != sendStatus) {
    emberAfGroupsClusterPrintln("Groups: failed to send %s response: 0x%x",
                                "view_group",
                                sendStatus);
  }
  return true;
}

bool emberAfGroupsClusterGetGroupMembershipCallback(uint8_t groupCount,
                                                    uint8_t *groupList)
{
  EmberStatus sendStatus;
  uint8_t i, j;
  uint8_t count = 0;
  uint8_t list[EMBER_BINDING_TABLE_SIZE << 1];
  uint8_t listLen = 0;

  emberAfGroupsClusterPrint("RX: GetGroupMembership 0x%x,", groupCount);
  for (i = 0; i < groupCount; i++) {
    emberAfGroupsClusterPrint(" [0x%2x]",
                              emberAfGetInt16u(groupList + (i << 1), 0, 2));
  }
  emberAfGroupsClusterPrintln("");

  // For all networks, Get Group Membership commands may be sent either
  // unicast or broadcast (removing the ZLL-specific limitation to unicast).

  // When Group Count is zero, respond with a list of all active groups.
  // Otherwise, respond with a list of matches.
  if (groupCount == 0) {
    for (i = 0; i < EMBER_BINDING_TABLE_SIZE; i++) {
      EmberBindingTableEntry entry;
      emberGetBinding(i, &entry);
      if (entry.type == EMBER_MULTICAST_BINDING) {
        if (entry.local == emberAfCurrentEndpoint()) {
          list[listLen]     = entry.identifier[0];
          list[listLen + 1] = entry.identifier[1];
          listLen += 2;
          count++;
        }
      }
    }
  } else {
    for (i = 0; i < groupCount; i++) {
      uint16_t groupId = emberAfGetInt16u(groupList + (i << 1), 0, 2);
      for (j = 0; j < EMBER_BINDING_TABLE_SIZE; j++) {
        EmberBindingTableEntry entry;
        emberGetBinding(j, &entry);
        if (entry.type == EMBER_MULTICAST_BINDING) {
          if (entry.local == emberAfCurrentEndpoint()
              && entry.identifier[0] == LOW_BYTE(groupId)
              && entry.identifier[1] == HIGH_BYTE(groupId)) {
            list[listLen]     = entry.identifier[0];
            list[listLen + 1] = entry.identifier[1];
            listLen += 2;
            count++;
          }
        }
      }
    }
  }

  // Only send a response if the Group Count was zero or if one or more active
  // groups matched.  Otherwise, a Default Response is sent.
  if (groupCount == 0 || count != 0) {
    // A capacity of 0xFF means that it is unknown if any further groups may be
    // added.  Each group requires a binding and, because the binding table is
    // used for other purposes besides groups, we can't be sure what the
    // capacity will be in the future.
    emberAfFillCommandGroupsClusterGetGroupMembershipResponse(0xFF, // capacity
                                                              count,
                                                              list,
                                                              listLen);
    sendStatus = emberAfSendResponse();
  } else {
    sendStatus = emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_NOT_FOUND);
  }
  if (EMBER_SUCCESS != sendStatus) {
    emberAfGroupsClusterPrintln("Groups: failed to send %s: 0x%x",
                                (groupCount == 0 || count != 0)
                                ? "get_group_membership response"
                                : "default_response",
                                sendStatus);
  }
  return true;
}

bool emberAfGroupsClusterRemoveGroupCallback(uint16_t groupId)
{
  EmberAfStatus status;
  EmberStatus sendStatus;

  emberAfGroupsClusterPrintln("RX: RemoveGroup 0x%2x", groupId);

  status = removeEntryFromGroupTable(emberAfCurrentEndpoint(),
                                     groupId);

  // For all networks, Remove Group commands are only responded to when
  // they are addressed to a single device.
  if (emberAfCurrentCommand()->type != EMBER_INCOMING_UNICAST
      && emberAfCurrentCommand()->type != EMBER_INCOMING_UNICAST_REPLY) {
    return true;
  }

  // EMAPPFWKV2-1414: if we remove a group, we should remove any scene
  // associated with it. ZCL6: 3.6.2.3.5: "Note that if a group is
  // removed the scenes associated with that group SHOULD be removed."
  emberAfScenesClusterRemoveScenesInGroupCallback(emberAfCurrentEndpoint(),
                                                  groupId);

  emberAfFillCommandGroupsClusterRemoveGroupResponse(status, groupId);
  sendStatus = emberAfSendResponse();
  if (EMBER_SUCCESS != sendStatus) {
    emberAfGroupsClusterPrintln("Groups: failed to send %s response: 0x%x",
                                "remove_group",
                                sendStatus);
  }
  return true;
}

bool emberAfGroupsClusterRemoveAllGroupsCallback(void)
{
  EmberStatus sendStatus;
  uint8_t i, endpoint = emberAfCurrentEndpoint();
  bool success = true;

  emberAfGroupsClusterPrintln("RX: RemoveAllGroups");

  for (i = 0; i < EMBER_BINDING_TABLE_SIZE; i++) {
    EmberBindingTableEntry binding;
    if (emberGetBinding(i, &binding) == EMBER_SUCCESS) {
      if (binding.type == EMBER_MULTICAST_BINDING
          && endpoint == binding.local) {
        EmberStatus status = emberDeleteBinding(i);
        if (status != EMBER_SUCCESS) {
          success = false;
          emberAfGroupsClusterPrintln("ERR: Failed to delete binding (0x%x)",
                                      status);
        } else {
          uint8_t groupName[ZCL_GROUPS_CLUSTER_MAXIMUM_NAME_LENGTH + 1] = { 0 };
          uint16_t groupId = HIGH_LOW_TO_INT(binding.identifier[1],
                                             binding.identifier[0]);
          emberAfPluginGroupsServerSetGroupNameCallback(endpoint,
                                                        groupId,
                                                        groupName);
          success = true && success;

          // EMAPPFWKV2-1414: if we remove a group, we should remove any scene
          // associated with it. ZCL6: 3.6.2.3.5: "Note that if a group is
          // removed the scenes associated with that group SHOULD be removed."
          emberAfScenesClusterRemoveScenesInGroupCallback(endpoint, groupId);
        }
      }
    }
  }

  emberAfScenesClusterRemoveScenesInGroupCallback(emberAfCurrentEndpoint(),
                                                  ZCL_SCENES_GLOBAL_SCENE_GROUP_ID);

  sendStatus = emberAfSendImmediateDefaultResponse(success
                                                   ? EMBER_ZCL_STATUS_SUCCESS
                                                   : EMBER_ZCL_STATUS_FAILURE);
  if (EMBER_SUCCESS != sendStatus) {
    emberAfGroupsClusterPrintln("Groups: failed to send %s: 0x%x",
                                "default_response",
                                sendStatus);
  }
  return true;
}

bool emberAfGroupsClusterAddGroupIfIdentifyingCallback(uint16_t groupId,
                                                       uint8_t *groupName)
{
  EmberAfStatus status;
  EmberStatus sendStatus;

  emberAfGroupsClusterPrint("RX: AddGroupIfIdentifying 0x%2x, \"", groupId);
  emberAfGroupsClusterPrintString(groupName);
  emberAfGroupsClusterPrintln("\"");

  if (!emberAfIsDeviceIdentifying(emberAfCurrentEndpoint())) {
    status = EMBER_ZCL_STATUS_FAILURE;
  } else {
    status = addEntryToGroupTable(emberAfCurrentEndpoint(),
                                  groupId,
                                  groupName);
  }

  sendStatus = emberAfSendImmediateDefaultResponse(status);
  if (EMBER_SUCCESS != sendStatus) {
    emberAfGroupsClusterPrintln("Groups: failed to send %s: 0x%x",
                                "default_response",
                                sendStatus);
  }
  return true;
}

bool emberAfGroupsClusterEndpointInGroupCallback(uint8_t endpoint,
                                                 uint16_t groupId)
{
  return isGroupPresent(endpoint, groupId);
}

void emberAfGroupsClusterClearGroupTableCallback(uint8_t endpoint)
{
  uint8_t i, networkIndex = emberGetCurrentNetwork();
  for (i = 0; i < EMBER_BINDING_TABLE_SIZE; i++) {
    EmberBindingTableEntry binding;
    if (emberGetBinding(i, &binding) == EMBER_SUCCESS
        && binding.type == EMBER_MULTICAST_BINDING
        && (endpoint == binding.local
            || (endpoint == EMBER_BROADCAST_ENDPOINT
                && networkIndex == binding.networkIndex))) {
      EmberStatus status = emberDeleteBinding(i);
      if (status != EMBER_SUCCESS) {
        emberAfGroupsClusterPrintln("ERR: Failed to delete binding (0x%x)",
                                    status);
      }
    }
  }
}

static bool isGroupPresent(uint8_t endpoint, uint16_t groupId)
{
  uint8_t i;

  for (i = 0; i < EMBER_BINDING_TABLE_SIZE; i++) {
    EmberBindingTableEntry binding;
    if (emberGetBinding(i, &binding) == EMBER_SUCCESS) {
      if (bindingGroupMatch(endpoint, groupId, &binding)) {
        return true;
      }
    }
  }

  return false;
}

static bool bindingGroupMatch(uint8_t endpoint,
                              uint16_t groupId,
                              EmberBindingTableEntry *entry)
{
  return (entry->type == EMBER_MULTICAST_BINDING
          && entry->identifier[0] == LOW_BYTE(groupId)
          && entry->identifier[1] == HIGH_BYTE(groupId)
          && entry->local == endpoint);
}

static uint8_t findGroupIndex(uint8_t endpoint, uint16_t groupId)
{
  uint8_t i;
  for (i = 0; i < EMBER_BINDING_TABLE_SIZE; i++) {
    EmberBindingTableEntry entry;
    emberGetBinding(i, &entry);
    if (bindingGroupMatch(endpoint, groupId, &entry)) {
      return i;
    }
  }
  return EMBER_AF_GROUP_TABLE_NULL_INDEX;
}
