/*
 * DACP protocol handler. This file is part of Shairport Sync.
 * Copyright (c) Mike Brady 2017 -- 2020
 * All rights reserved.
 *
 * 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.
 */

// Information about the four-character codes is from many sources, with thanks, including
// https://github.com/melloware/dacp-net/blob/master/Melloware.DACP/

#include "dacp.h"
#include "common.h"
#include "config.h"

#include <arpa/inet.h>
#include <errno.h>
#include <inttypes.h>
#include <math.h>
#include <memory.h>
#include <netdb.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdlib.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>

#include "metadata_hub.h"
#include "tinyhttp/http.h"

typedef struct {
  int players_connection_thread_index; // the connection thread index when a player thread is
                                       // associated with this, zero otherwise
  int scan_enable;                     // set to 1 if scanning should be considered
  char dacp_id[256];                   // the DACP ID string
  uint16_t port;                       // zero if no port discovered
  short connection_family;             // AF_INET6 or AF_INET
  int always_use_revision_number_1;    // for dealing with forked-daapd;
  uint32_t scope_id;                   // if it's an ipv6 connection, this will be its scope id
  char ip_string[INET6_ADDRSTRLEN];    // the ip string pointing to the client
  char *active_remote_id;              // send this when you want to send remote control commands
  void *port_monitor_private_storage;
} dacp_server_record;

int dacp_monitor_initialised = 0;
pthread_t dacp_monitor_thread;
dacp_server_record dacp_server;
void *mdns_dacp_monitor_private_storage_pointer;

// HTTP Response data/funcs (See the tinyhttp example.cpp file for more on this.)
struct HttpResponse {
  void *body;            // this will be a malloc'ed pointer
  ssize_t malloced_size; // this will be its allocated size
  ssize_t size;          // the current size of the content
  int code;
};

void *response_realloc(__attribute__((unused)) void *opaque, void *ptr, int size) {
  void *t = realloc(ptr, size);
  if ((t == NULL) && (size != 0))
    debug(1, "Response realloc of size %d failed!", size);
  return t;
}

void response_body(void *opaque, const char *data, int size) {
  struct HttpResponse *response = (struct HttpResponse *)opaque;

  ssize_t space_available = response->malloced_size - response->size;
  if (space_available < size) {
    // debug(1,"Getting more space for the response -- need %d bytes but only %ld bytes left.\n",
    // size,
    //       size - space_available);
    ssize_t size_requested = size - space_available + response->malloced_size + 16384;
    void *t = realloc(response->body, size_requested);
    response->malloced_size = size_requested;
    if (t)
      response->body = t;
    else {
      die("dacp: can't allocate any more space for parser.");
    }
  }
  memcpy(response->body + response->size, data, size);
  response->size += size;
}

static void
response_header(__attribute__((unused)) void *opaque, __attribute__((unused)) const char *ckey,
                __attribute__((unused)) int nkey, __attribute__((unused)) const char *cvalue,
                __attribute__((unused)) int nvalue) { /* example doesn't care about headers */
}

static void response_code(void *opaque, int code) {
  struct HttpResponse *response = (struct HttpResponse *)opaque;
  response->code = code;
}

static const struct http_funcs responseFuncs = {
    response_realloc,
    response_body,
    response_header,
    response_code,
};

// static pthread_mutex_t dacp_conversation_lock = PTHREAD_MUTEX_INITIALIZER;
// static pthread_mutex_t dacp_server_information_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t dacp_conversation_lock;
static pthread_mutex_t dacp_server_information_lock;
static pthread_cond_t dacp_server_information_cv;

void addrinfo_cleanup(void *arg) {
  // debug(1, "addrinfo cleanup called.");
  struct addrinfo **info = (struct addrinfo **)arg;
  freeaddrinfo(*info);
}

void mutex_lock_cleanup(void *arg) {
  pthread_mutex_t *m = (pthread_mutex_t *)arg;
  if (pthread_mutex_unlock(m))
    debug(1, "Error releasing mutex.");
}

void connect_cleanup(void *arg) {
  int *fd = (int *)arg;
  // debug(2, "dacp_send_command: close socket %d.",*fd);
  close(*fd);
}

void http_cleanup(void *arg) {
  // debug(1, "http cleanup called.");
  struct http_roundtripper *rt = (struct http_roundtripper *)arg;
  http_free(rt);
}

int dacp_send_command(const char *command, char **body, ssize_t *bodysize) {
  int result;
  // debug(1,"dacp_send_command: command is: \"%s\".",command);

  if (dacp_server.port == 0) {
    // debug(3, "No DACP port specified yet");
    result = 490; // no port specified
  } else {

    // will malloc space for the body or set it to NULL -- the caller should free it.

    // Using some custom HTTP-like return codes
    //  498 Bad Address information for the DACP server
    //  497 Can't establish a socket to the DACP server
    //  496 Can't connect to the DACP server
    //  495 Error receiving response
    //  494 This client is already busy
    //  493 Client failed to send a message
    //  492 Argument out of range
    //  491 Client refused connection
    //  490 No port specified

    struct addrinfo hints, *res;
    int sockfd;

    struct HttpResponse response;
    response.body = NULL;
    response.malloced_size = 0;
    response.size = 0;
    response.code = 0;

    char portstring[10], server[1024], message[1024];
    memset(&portstring, 0, sizeof(portstring));
    if (dacp_server.connection_family == AF_INET6) {
      snprintf(server, sizeof(server), "%s%%%u", dacp_server.ip_string, dacp_server.scope_id);
    } else {
      strcpy(server, dacp_server.ip_string);
    }
    snprintf(portstring, sizeof(portstring), "%u", dacp_server.port);

    // first, load up address structs with getaddrinfo():

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;

    // debug(1, "DACP port string is \"%s:%s\".", server, portstring);

    int ires = getaddrinfo(server, portstring, &hints, &res);
    if (ires) {
      // debug(1,"Error %d \"%s\" at getaddrinfo.",ires,gai_strerror(ires));
      response.code = 498; // Bad Address information for the DACP server
    } else {
      uint64_t start_time = get_absolute_time_in_ns();
      pthread_cleanup_push(addrinfo_cleanup, (void *)&res);
      // only do this one at a time -- not sure it is necessary, but better safe than sorry

      int mutex_reply = sps_pthread_mutex_timedlock(&dacp_conversation_lock, 2000000, command, 1);
      // int mutex_reply = pthread_mutex_lock(&dacp_conversation_lock);
      if (mutex_reply == 0) {
        pthread_cleanup_push(mutex_lock_cleanup, (void *)&dacp_conversation_lock);

        // make a socket:
        sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

        if (sockfd == -1) {
          // debug(1, "DACP socket could not be created -- error %d:
          // \"%s\".",errno,strerror(errno));
          response.code = 497; // Can't establish a socket to the DACP server
        } else {
          pthread_cleanup_push(connect_cleanup, (void *)&sockfd);
          // debug(2, "dacp_send_command: open socket %d.",sockfd);

          // This is for limiting the time to be spent waiting for a response.

          struct timeval tv;
          tv.tv_sec = 0;
          tv.tv_usec = 500000;
          if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1)
            debug(1, "dacp_send_command: error %d setting receive timeout.", errno);
          if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof tv) == -1)
            debug(1, "dacp_send_command: error %d setting send timeout.", errno);

          // connect!
          // debug(1, "DACP socket created.");
          if (connect(sockfd, res->ai_addr, res->ai_addrlen) < 0) {
            // debug(1, "dacp_send_command: connect failed with errno %d.", errno);
            if (errno == ECONNREFUSED)
              response.code = 491; // DACP server doesn't want to talk anymore...
            else
              response.code = 496; // Can't connect to the DACP server
          } else {
            // debug(1,"DACP connect succeeded.");

            snprintf(message, sizeof(message),
                     "GET /ctrl-int/1/%s HTTP/1.1\r\nHost: %s:%u\r\nActive-Remote: %s\r\n\r\n",
                     command, dacp_server.ip_string, dacp_server.port,
                     dacp_server.active_remote_id);

            // Send command
            debug(3, "dacp_send_command: \"%s\".", command);
            ssize_t wresp = send(sockfd, message, strlen(message), 0);
            if (wresp == -1) {
              char errorstring[1024];
              strerror_r(errno, (char *)errorstring, sizeof(errorstring));
              debug(2, "dacp_send_command: write error %d: \"%s\".", errno, (char *)errorstring);
              struct linger so_linger;
              so_linger.l_onoff = 1; // "true"
              so_linger.l_linger = 0;
              int err = setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger);
              if (err)
                debug(1, "Could not set the dacp socket to abort due to a write error on closing.");
            }
            if (wresp != (ssize_t)strlen(message)) {
              // debug(1, "dacp_send_command: send failed.");
              response.code = 493; // Client failed to send a message

            } else {

              response.body = malloc(2048); // it can resize this if necessary
              response.malloced_size = 2048;
              pthread_cleanup_push(malloc_cleanup, response.body);

              struct http_roundtripper rt;
              http_init(&rt, responseFuncs, &response);
              pthread_cleanup_push(http_cleanup, &rt);

              int needmore = 1;
              int looperror = 0;
              char buffer[8192];
              memset(buffer, 0, sizeof(buffer));
              while (needmore && !looperror) {
                const char *data = buffer;
                if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof tv) == -1)
                  debug(1, "dacp_send_command: error %d setting receive timeout.", errno);
                ssize_t ndata = recv(sockfd, buffer, sizeof(buffer), 0);
                // debug(3, "Received %d bytes: \"%s\".", ndata, buffer);
                if (ndata <= 0) {
                  if (ndata == -1) {
                    char errorstring[1024];
                    strerror_r(errno, (char *)errorstring, sizeof(errorstring));
                    debug(2, "dacp_send_command: receiving error %d: \"%s\".", errno,
                          (char *)errorstring);
                    struct linger so_linger;
                    so_linger.l_onoff = 1; // "true"
                    so_linger.l_linger = 0;
                    int err =
                        setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger);
                    if (err)
                      debug(
                          1,
                          "Could not set the dacp socket to abort due to a read error on closing.");
                  }

                  free(response.body);
                  response.body = NULL;
                  response.malloced_size = 0;
                  response.size = 0;
                  response.code = 495; // Error receiving response
                  looperror = 1;
                }

                while (needmore && ndata && !looperror) {
                  int read;
                  needmore = http_data(&rt, data, ndata, &read);
                  ndata -= read;
                  data += read;
                }
              }

              if (http_iserror(&rt)) {
                debug(3, "dacp_send_command: error parsing data.");
                free(response.body);
                response.body = NULL;
                response.malloced_size = 0;
                response.size = 0;
              }
              // debug(1,"Size of response body is %d",response.size);
              pthread_cleanup_pop(1); // this should call http_cleanup
              // http_free(&rt);
              pthread_cleanup_pop(
                  0); // this should *not* free the malloced buffer -- just pop the malloc cleanup
            }
          }
          pthread_cleanup_pop(1); // this should close the socket
                                  // close(sockfd);
                                  // debug(1,"DACP socket closed.");
        }
        pthread_cleanup_pop(1); // this should unlock the dacp_conversation_lock);
        // pthread_mutex_unlock(&dacp_conversation_lock);
        // debug(1,"Sent command\"%s\" with a response body of size %d.",command,response.size);
        // debug(1,"dacp_conversation_lock released.");
      } else {
        debug(3,
              "dacp_send_command: could not acquire a lock on the dacp transmit/receive section "
              "when attempting to "
              "send the command \"%s\". Possible timeout?",
              command);
        response.code = 494; // This client is already busy
      }
      pthread_cleanup_pop(1); // this should free the addrinfo
      // freeaddrinfo(res);
      uint64_t et = get_absolute_time_in_ns() - start_time; // this will be in nanoseconds
      debug(3, "dacp_send_command: %f seconds, response code %d, command \"%s\".",
            (1.0 * et) / 1000000000, response.code, command);
    }
    *body = response.body;
    *bodysize = response.size;
    result = response.code;
  }
  return result;
}

int send_simple_dacp_command(const char *command) {
  int reply = 0;
  char *server_reply = NULL;
  debug(2, "send_simple_dacp_command: sending command \"%s\".", command);
  ssize_t reply_size = 0;
  reply = dacp_send_command(command, &server_reply, &reply_size);
  if (server_reply) {
    free(server_reply);
    server_reply = NULL;
  }
  return reply;
}

void relinquish_dacp_server_information(rtsp_conn_info *conn) {
  // this will set the dacp_server.players_connection_thread_index to zero iff it has the same value
  // as the conn's connection number
  // this is to signify that the player has stopped, but only if another thread (with a different
  // index) hasn't already taken over the dacp service
  debug_mutex_lock(&dacp_server_information_lock, 500000, 2);
  if (dacp_server.players_connection_thread_index == conn->connection_number)
    dacp_server.players_connection_thread_index = 0;
  debug_mutex_unlock(&dacp_server_information_lock, 3);
}

// this will be running on the thread of its caller, not of the conversation thread...
// tell the DACP conversation thread what DACP server we are listening to
// if the active_remote_id is the same we don't change anything apart from
// the conversation number
// Thus, we can keep the DACP port that might have previously been discovered
void set_dacp_server_information(rtsp_conn_info *conn) {
  // debug(1, "set_dacp_server_information");
  debug_mutex_lock(&dacp_server_information_lock, 500000, 2);
  dacp_server.players_connection_thread_index = conn->connection_number;

  if ((conn->dacp_id == NULL) || (strcmp(conn->dacp_id, dacp_server.dacp_id) != 0)) {
    if (conn->dacp_id)
      strncpy(dacp_server.dacp_id, conn->dacp_id, sizeof(dacp_server.dacp_id) - 1);
    else
      dacp_server.dacp_id[0] = '\0';
    dacp_server.port = 0;
    dacp_server.scan_enable = 0;
    dacp_server.connection_family = conn->connection_ip_family;
    dacp_server.scope_id = conn->self_scope_id;
    strncpy(dacp_server.ip_string, conn->client_ip_string, INET6_ADDRSTRLEN);
    debug(2, "set_dacp_server_information set IP to \"%s\" and DACP id to \"%s\".",
          dacp_server.ip_string, dacp_server.dacp_id);

    // If the client is forked-daapd, then we always use revision number 1
    // because otherwise the return read will hang in a "long poll" if there
    // are no changes.
    // This is different to other AirPlay clients
    // which return immediately with a 403 code if there are no changes.
    dacp_server.always_use_revision_number_1 = 0;
    char *p = strstr(conn->UserAgent, "forked-daapd");
    if ((p != 0) &&
        (p == conn->UserAgent)) { // must exist and be at the start of the UserAgent string
      dacp_server.always_use_revision_number_1 = 1;
    }

    metadata_hub_modify_prolog();
    int ch = metadata_store.dacp_server_active != dacp_server.scan_enable;
    metadata_store.dacp_server_active = dacp_server.scan_enable;
    if ((metadata_store.client_ip == NULL) ||
        (strncmp(metadata_store.client_ip, conn->client_ip_string, INET6_ADDRSTRLEN) != 0)) {
      if (metadata_store.client_ip)
        free(metadata_store.client_ip);
      metadata_store.client_ip = strndup(conn->client_ip_string, INET6_ADDRSTRLEN);
      debug(3, "MH Client IP set to: \"%s\"", metadata_store.client_ip);
      ch = 1;
    }
    metadata_hub_modify_epilog(ch);
  } else {
    if (dacp_server.port) {
      // debug(1, "Re-enable scanning.");
      dacp_server.scan_enable = 1;
      //      metadata_hub_modify_prolog();
      //      int ch = metadata_store.dacp_server_active != dacp_server.scan_enable;
      //      metadata_store.dacp_server_active = dacp_server.scan_enable;
      //      metadata_hub_modify_epilog(ch);
    }
  }
  if (dacp_server.active_remote_id)
    free(dacp_server.active_remote_id);
  if (conn->dacp_active_remote)
    dacp_server.active_remote_id =
        strdup(conn->dacp_active_remote); // even if the dacp_id remains the same,
                                          // the active remote will change.
  else
    dacp_server.active_remote_id = NULL;

  debug(3, "set_dacp_server_information set active-remote id to %s.", dacp_server.active_remote_id);
  pthread_cond_signal(&dacp_server_information_cv);
  debug_mutex_unlock(&dacp_server_information_lock, 3);
}

void dacp_monitor_port_update_callback(char *dacp_id, uint16_t port) {
  debug_mutex_lock(&dacp_server_information_lock, 500000, 2);
  debug(2,
        "dacp_monitor_port_update_callback with Remote ID \"%s\", target ID \"%s\" and port "
        "number %d.",
        dacp_id, dacp_server.dacp_id, port);
  if ((dacp_id == NULL) || (dacp_server.dacp_id == NULL)) {
    warn("dacp_id or dacp_server.dacp_id NULL detected");
  } else {
    if (strcmp(dacp_id, dacp_server.dacp_id) == 0) {
      dacp_server.port = port;
      if (port == 0)
        dacp_server.scan_enable = 0;
      else {
        dacp_server.scan_enable = 1;
        // debug(2, "dacp_monitor_port_update_callback enables scan");
      }
      //    metadata_hub_modify_prolog();
      //    int ch = metadata_store.dacp_server_active != dacp_server.scan_enable;
      //    metadata_store.dacp_server_active = dacp_server.scan_enable;
      //    metadata_hub_modify_epilog(ch);
    } else {
      debug(1, "dacp port monitor reporting on an out-of-use remote.");
    }
  }
  pthread_cond_signal(&dacp_server_information_cv);
  debug_mutex_unlock(&dacp_server_information_lock, 3);
}

void dacp_monitor_thread_code_cleanup(__attribute__((unused)) void *arg) {
  // debug(1, "dacp_monitor_thread_code_cleanup called.");
  pthread_mutex_unlock(&dacp_server_information_lock);
}

void *dacp_monitor_thread_code(__attribute__((unused)) void *na) {
  int scan_index = 0;
  int always_use_revision_number_1 = 0;
  // char server_reply[10000];
  // debug(1, "DACP monitor thread started.");
  // wait until we get a valid port number to begin monitoring it
  int32_t revision_number = 1;
  int bad_result_count = 0;
  int idle_scan_count = 0;
  while (1) {
    int result = 0;
    sps_pthread_mutex_timedlock(
        &dacp_server_information_lock, 500000,
        "dacp_monitor_thread_code couldn't get DACP server information lock in 0.5 second!.", 2);
    int32_t the_volume;

    pthread_cleanup_push(dacp_monitor_thread_code_cleanup, NULL);
    if (dacp_server.scan_enable == 0) {
      metadata_hub_modify_prolog();
      int ch = (metadata_store.dacp_server_active != 0) ||
               (metadata_store.advanced_dacp_server_active != 0);
      metadata_store.dacp_server_active = 0;
      metadata_store.advanced_dacp_server_active = 0;
      /*
      debug(3,
            "setting metadata_store.dacp_server_active and "
            "metadata_store.advanced_dacp_server_active to 0 with an update "
            "flag value of %d",
            ch);
      */
      metadata_hub_modify_epilog(ch);

      uint64_t time_to_wait_for_wakeup_ns =
          (uint64_t)(1000000000 * config.missing_port_dacp_scan_interval_seconds);

#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
      uint64_t time_of_wakeup_ns = get_absolute_time_in_ns() + time_to_wait_for_wakeup_ns;
      uint64_t sec = time_of_wakeup_ns / 1000000000;
      uint64_t nsec = time_of_wakeup_ns % 1000000000;
#endif
#ifdef COMPILE_FOR_OSX
      uint64_t sec = time_to_wait_for_wakeup_ns / 1000000000;
      uint64_t nsec = time_to_wait_for_wakeup_ns % 1000000000;
#endif

      struct timespec time_to_wait;
      time_to_wait.tv_sec = sec;
      time_to_wait.tv_nsec = nsec;

      while ((dacp_server.scan_enable == 0) && (result != ETIMEDOUT)) {
        // debug(1, "dacp_monitor_thread_code wait for an event to possibly enable scan");

#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
        result = pthread_cond_timedwait(&dacp_server_information_cv, &dacp_server_information_lock,
                                        &time_to_wait); // this is a pthread cancellation point
        // debug(1, "result is %d, dacp_server.scan_enable is %d, time_to_wait_for_wakeup_ns is %"
        // PRId64 ".", result, dacp_server.scan_enable, time_to_wait_for_wakeup_ns);

#endif
#ifdef COMPILE_FOR_OSX
        result = pthread_cond_timedwait_relative_np(&dacp_server_information_cv,
                                                    &dacp_server_information_lock, &time_to_wait);
#endif
      }
      if (dacp_server.scan_enable == 1) {
        bad_result_count = 0;
        idle_scan_count = 0;
      }
    }

    always_use_revision_number_1 =
        dacp_server.always_use_revision_number_1; // set this while access is locked

    result = dacp_get_volume(&the_volume); // just want the http code
    pthread_cleanup_pop(1);

    if (result == 490) { // 490 means no port was specified
      if (((char *)dacp_server.dacp_id != NULL) && (strlen(dacp_server.dacp_id) != 0)) {
        // debug(1,"mdns_dacp_monitor_set_id");
        mdns_dacp_monitor_set_id(dacp_server.dacp_id);
      }
    } else {
      scan_index++;
      // debug(1,"DACP Scan Result: %d.", result);

      if ((result == 200) || (result == 400)) {
        bad_result_count = 0;
      } else {
        if (bad_result_count < config.scan_max_bad_response_count) // limit to some reasonable value
          bad_result_count++;
      }

      // here, do the debouncing calculations to see
      // if the dacp server  and the
      // advanced dacp server are available

      // -1 means we don't know because some bad statuses have been reported
      // 0 means definitely no
      // +1 means definitely yes

      int dacp_server_status_now = -1;
      int advanced_dacp_server_status_now = -1;

      if (bad_result_count == 0) {
        dacp_server_status_now = 1;
        if (result == 200)
          advanced_dacp_server_status_now = 1;
        else if (result == 400)
          advanced_dacp_server_status_now = 0;
      } else if (bad_result_count ==
                 config.scan_max_bad_response_count) { // if a sequence of bad return codes occurs,
                                                       // then it's gone
        dacp_server_status_now = 0;
        advanced_dacp_server_status_now = 0;
      }

      if (metadata_store.player_thread_active == 0)
        idle_scan_count++;
      else
        idle_scan_count = 0;

      debug(3, "Scan Result: %d, Bad Scan Count: %d, Idle Scan Count: %d.", result,
            bad_result_count, idle_scan_count);

      /* not used
      // decide if idle for too long
          if  (idle_scan_count == config.scan_max_inactive_count) {
            debug(2, "DACP server status scanning stopped.");
            dacp_server.scan_enable = 0;
          }
      */

      int update_needed = 0;
      metadata_hub_modify_prolog();
      if (dacp_server_status_now != -1) { // if dacp_server_status_now is actually known...
        if (metadata_store.dacp_server_active != dacp_server_status_now) {
          debug(2, "metadata_store.dacp_server_active set to %d.", dacp_server_status_now);
          metadata_store.dacp_server_active = dacp_server_status_now;
          update_needed = 1;
        }
      }
      if (advanced_dacp_server_status_now !=
          -1) { // if advanced_dacp_server_status_now is actually known...
        if (metadata_store.advanced_dacp_server_active != advanced_dacp_server_status_now) {
          debug(2, "metadata_store.advanced_dacp_server_active set to %d.", dacp_server_status_now);
          metadata_store.advanced_dacp_server_active = advanced_dacp_server_status_now;
          update_needed = 1;
        }
      }

      metadata_hub_modify_epilog(update_needed);

      // pthread_mutex_unlock(&dacp_server_information_lock);
      // debug(1, "DACP Server ID \"%u\" at \"%s:%u\", scan %d.", dacp_server.active_remote_id,
      //      dacp_server.ip_string, dacp_server.port, scan_index);

      if (result == 200) {
        metadata_hub_modify_prolog();
        int diff = metadata_store.speaker_volume != the_volume;
        if (diff)
          metadata_store.speaker_volume = the_volume;
        metadata_hub_modify_epilog(diff);

        ssize_t le;
        char *response = NULL;
        int32_t item_size;
        char command[1024] = "";
        if (always_use_revision_number_1 != 0) // for forked-daapd
          revision_number = 1;
        snprintf(command, sizeof(command) - 1, "playstatusupdate?revision-number=%d",
                 revision_number);
        // debug(1,"dacp_monitor_thread_code: command: \"%s\"",command);
        result = dacp_send_command(command, &response, &le);
        // debug(1,"Response to \"%s\" is %d.",command,result);
        // remember: unless the revision_number you pass in is 1,
        // response will be 200 only if there's something new to report.
        if (result == 200) {
          // if (0) {
          char *sp = response;
          if (le >= 8) {
            // here start looking for the contents of the status update
            if (dacp_tlv_crawl(&sp, &item_size) == 'cmst') { // status
              // here, we know that we are receiving playerstatusupdates, so set a flag
              metadata_hub_modify_prolog();
              // debug(1, "playstatusupdate release track metadata");
              // metadata_hub_reset_track_metadata();
              // metadata_store.playerstatusupdates_are_received = 1;
              sp -= item_size; // drop down into the array -- don't skip over it
              le -= 8;
              // char typestring[5];
              // we need to acquire the metadata data structure and possibly update it
              while (le >= 8) {
                uint32_t type = dacp_tlv_crawl(&sp, &item_size);
                le -= item_size + 8;
                char *t;
                // char u;
                // char *st;
                int32_t r;
                uint32_t ui;
                // uint64_t v;
                // int i;

                switch (type) {
                case 'cmsr': // revision number
                  t = sp - item_size;
                  revision_number = ntohl(*(uint32_t *)(t));
                  // debug(1,"New revision number received: %d", revision_number);
                  break;
                case 'caps': // play status
                  t = sp - item_size;
                  r = *(unsigned char *)(t);
                  switch (r) {
                  case 2:
                    if (metadata_store.play_status != PS_STOPPED) {
                      metadata_store.play_status = PS_STOPPED;
                      debug(2, "Play status is \"stopped\".");
                    }
                    break;
                  case 3:
                    if (metadata_store.play_status != PS_PAUSED) {
                      metadata_store.play_status = PS_PAUSED;
                      debug(2, "Play status is \"paused\".");
                    }
                    break;
                  case 4:
                    if (metadata_store.play_status != PS_PLAYING) {
                      metadata_store.play_status = PS_PLAYING;
                      debug(2, "Play status changed to \"playing\".");
                    }
                    break;
                  default:
                    debug(1, "Unrecognised play status %d received.", r);
                    break;
                  }
                  break;
                case 'cash': // shuffle status
                  t = sp - item_size;
                  r = *(unsigned char *)(t);
                  switch (r) {
                  case 0:
                    if (metadata_store.shuffle_status != SS_OFF) {
                      metadata_store.shuffle_status = SS_OFF;
                      debug(2, "Shuffle status is \"off\".");
                    }
                    break;
                  case 1:
                    if (metadata_store.shuffle_status != SS_ON) {
                      metadata_store.shuffle_status = SS_ON;
                      debug(2, "Shuffle status is \"on\".");
                    }
                    break;
                  default:
                    debug(1, "Unrecognised shuffle status %d received.", r);
                    break;
                  }
                  break;
                case 'carp': // repeat status
                  t = sp - item_size;
                  r = *(unsigned char *)(t);
                  switch (r) {
                  case 0:
                    if (metadata_store.repeat_status != RS_OFF) {
                      metadata_store.repeat_status = RS_OFF;
                      debug(2, "Repeat status is \"none\".");
                    }
                    break;
                  case 1:
                    if (metadata_store.repeat_status != RS_ONE) {
                      metadata_store.repeat_status = RS_ONE;
                      debug(2, "Repeat status is \"one\".");
                    }
                    break;
                  case 2:
                    if (metadata_store.repeat_status != RS_ALL) {
                      metadata_store.repeat_status = RS_ALL;
                      debug(2, "Repeat status is \"all\".");
                    }
                    break;
                  default:
                    debug(1, "Unrecognised repeat status %d received.", r);
                    break;
                  }
                  break;
                case 'cann': // track name
                  debug(2, "DACP Track Name seen");
                  if (string_update_with_size(&metadata_store.track_name,
                                              &metadata_store.track_name_changed, sp - item_size,
                                              item_size)) {
                    debug(2, "DACP Track Name set to: \"%s\"", metadata_store.track_name);
                  }
                  break;
                case 'cana': // artist name
                  debug(2, "DACP Artist Name seen");
                  if (string_update_with_size(&metadata_store.artist_name,
                                              &metadata_store.artist_name_changed, sp - item_size,
                                              item_size)) {
                    debug(2, "DACP Artist Name set to: \"%s\"", metadata_store.artist_name);
                  }
                  break;
                case 'canl': // album name
                  debug(2, "DACP Album Name seen");
                  if (string_update_with_size(&metadata_store.album_name,
                                              &metadata_store.album_name_changed, sp - item_size,
                                              item_size)) {
                    debug(2, "DACP Album Name set to: \"%s\"", metadata_store.album_name);
                  }
                  break;
                case 'cang': // genre
                  debug(2, "DACP Genre seen");
                  if (string_update_with_size(&metadata_store.genre, &metadata_store.genre_changed,
                                              sp - item_size, item_size)) {
                    debug(2, "DACP Genre set to: \"%s\"", metadata_store.genre);
                  }
                  break;
                case 'canp': // nowplaying 4 ids: dbid, plid, playlistItem, itemid (from mellowware
                             // see reference above)
                  debug(2, "DACP Composite ID seen");
                  if (memcmp(metadata_store.item_composite_id, sp - item_size,
                             sizeof(metadata_store.item_composite_id)) != 0) {
                    memcpy(metadata_store.item_composite_id, sp - item_size,
                           sizeof(metadata_store.item_composite_id));
                    char st[33];
                    char *pt = st;
                    int it;
                    for (it = 0; it < 16; it++) {
                      snprintf(pt, 3, "%02X", metadata_store.item_composite_id[it]);
                      pt += 2;
                    }
                    *pt = 0;
                    debug(2, "Item composite ID changed to 0x%s.", st);
                    metadata_store.item_composite_id_changed = 1;
                  }
                  break;
                case 'astm':
                  t = sp - item_size;
                  ui = ntohl(*(uint32_t *)(t));
                  debug(2, "DACP Song Time seen: \"%u\" of length %u.", ui, item_size);
                  if (ui != metadata_store.songtime_in_milliseconds) {
                    metadata_store.songtime_in_milliseconds = ui;
                    metadata_store.songtime_in_milliseconds_changed = 1;
                    debug(2, "DACP Song Time set to: \"%u\"",
                          metadata_store.songtime_in_milliseconds);
                  }
                  break;

                /*
                            case 'mstt':
                            case 'cant':
                            case 'cast':
                            case 'cmmk':
                            case 'caas':
                            case 'caar':
                              t = sp - item_size;
                              r = ntohl(*(uint32_t *)(t));
                              printf("    %d", r);
                              printf("    (0x");
                              t = sp - item_size;
                              for (i = 0; i < item_size; i++) {
                                printf("%02x", *t & 0xff);
                                t++;
                              }
                              printf(")");
                              break;
                            case 'asai':
                              t = sp - item_size;
                              s = ntohl(*(uint32_t *)(t));
                              s = s << 32;
                              t += 4;
                              v = (ntohl(*(uint32_t *)(t))) & 0xffffffff;
                              s += v;
                              printf("    %lu", s);
                              printf("    (0x");
                              t = sp - item_size;
                              for (i = 0; i < item_size; i++) {
                                printf("%02x", *t & 0xff);
                                t++;
                              }
                              printf(")");
                              break;
                 */
                default:
                  /*
                    printf("    0x");
                    t = sp - item_size;
                    for (i = 0; i < item_size; i++) {
                      printf("%02x", *t & 0xff);
                      t++;
                    }
                   */
                  break;
                }
                // printf("\n");
              }

              // finished possibly writing to the metadata hub
              metadata_hub_modify_epilog(
                  1); // should really see if this can be made responsive to changes
            } else {
              debug(1, "Status Update not found.\n");
            }
          } else {
            debug(1, "Can't find any content in playerstatusupdate request");
          }
        } /* else {
          if (result != 403)
            debug(1, "Unexpected response %d to playerstatusupdate request", result);
        } */
        if (response) {
          free(response);
          response = NULL;
        };
      };
      /*
      strcpy(command,"nowplayingartwork?mw=320&mh=320");
      debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
      if (response) {
        free(response);
        response = NULL;
      }
      strcpy(command,"getproperty?properties=dmcp.volume");
      debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
      if (response) {
        free(response);
        response = NULL;
      }
      strcpy(command,"setproperty?dmcp.volume=100.000000");
      debug(1,"Command: \"%s\", result is %d",command, dacp_send_command(command, &response, &le));
      if (response) {
        free(response);
        response = NULL;
      }
      */
      if (metadata_store.player_thread_active)
        sleep(config.scan_interval_when_active);
      else
        sleep(config.scan_interval_when_inactive);
    }
  }
  debug(1, "DACP monitor thread exiting -- should never happen.");
  pthread_exit(NULL);
}

void dacp_monitor_start() {
  int rc;

#ifdef COMPILE_FOR_LINUX_AND_FREEBSD_AND_CYGWIN_AND_OPENBSD
  pthread_condattr_t attr;
  pthread_condattr_init(&attr);
  pthread_condattr_setclock(&attr, CLOCK_MONOTONIC); // can't do this in OS X, and don't need it.
  rc = pthread_cond_init(&dacp_server_information_cv, &attr);
  if (rc)
    debug(1, "Error initialising the DACP Server Information Condition Variable");
  pthread_condattr_destroy(&attr);
#endif
#ifdef COMPILE_FOR_OSX
  rc = pthread_cond_init(&dacp_server_information_cv, NULL);
#endif

  pthread_mutexattr_t mta;

  rc = pthread_mutexattr_init(&mta);
  if (rc)
    debug(1, "Error creating the DACP Conversation Lock Mutex Att Init");

  rc = pthread_mutexattr_settype(&mta, PTHREAD_MUTEX_ERRORCHECK);
  if (rc)
    debug(1, "Error creating the DACP Conversation Lock Mutex Errorcheck");

  // rc = pthread_mutexattr_setname_np(&mta, "DACP Conversation Lock");
  // if (rc)
  //  debug(1,"Error creating the DACP Conversation Lock Mutex Set Name");

  rc = pthread_mutex_init(&dacp_conversation_lock, &mta);
  if (rc)
    debug(1, "Error creating the DACP Conversation Lock Mutex Init");
  // else
  //  debug(1, "DACP Conversation Lock Mutex Init");

  rc = pthread_mutexattr_destroy(&mta);
  if (rc)
    debug(1, "Error creating the DACP Conversation Lock Attr Destroy");

  rc = pthread_mutexattr_init(&mta);
  if (rc)
    debug(1, "Error creating the DACP Server Information Lock Mutex Att Init");

  rc = pthread_mutexattr_settype(&mta, PTHREAD_MUTEX_ERRORCHECK);
  if (rc)
    debug(1, "Error creating the DACP Server Information Lock Mutex Errorcheck");

  // rc = pthread_mutexattr_setname_np(&mta, "DACP Conversation Lock");
  // if (rc)
  //  debug(1,"Error creating the DACP Server Information Lock Mutex Set Name");

  rc = pthread_mutex_init(&dacp_server_information_lock, &mta);
  if (rc)
    debug(1, "Error creating the DACP Server Information Lock Mutex Init");

  rc = pthread_mutexattr_destroy(&mta);
  if (rc)
    debug(1, "Error creating the DACP Server Information Lock Attr Destroy");

  memset(&dacp_server, 0, sizeof(dacp_server_record));

  pthread_create(&dacp_monitor_thread, NULL, dacp_monitor_thread_code, NULL);
  dacp_monitor_initialised = 1;
}

void dacp_monitor_stop() {
  if (dacp_monitor_initialised) { // only if it's been started and initialised
    debug(2, "dacp_monitor_stop");
    pthread_cancel(dacp_monitor_thread);
    pthread_join(dacp_monitor_thread, NULL);
    pthread_mutex_destroy(&dacp_server_information_lock);
    debug(3, "DACP Conversation Lock Mutex Destroyed");
    pthread_mutex_destroy(&dacp_conversation_lock);
    pthread_cond_destroy(&dacp_server_information_cv);
    debug(3, "DACP Server Information Condition Variable destroyed.");
    if (dacp_server.active_remote_id) {
      free(dacp_server.active_remote_id);
      dacp_server.active_remote_id = NULL;
    }
  }
}

uint32_t dacp_tlv_crawl(char **p, int32_t *length) {
  char typecode[5];
  memcpy(typecode, *p, 4);
  typecode[4] = '\0';
  uint32_t type = ntohl(*(uint32_t *)*p);
  *p += 4;
  *length = ntohl(*(uint32_t *)*p);
  *p += 4 + *length;
  // debug(1,"Type seen: '%s' of length %d",typecode,*length);
  return type;
}

int dacp_get_client_volume(int32_t *result) {
  // debug(1,"dacp_get_client_volume");
  char *server_reply = NULL;
  int32_t overall_volume = -1;
  ssize_t reply_size;
  // debug(1,"dacp_get_client_volume: dacp_send_command");
  int response =
      dacp_send_command("getproperty?properties=dmcp.volume", &server_reply, &reply_size);
  if (response == 200) { // if we get an okay
    char *sp = server_reply;
    int32_t item_size;
    if (reply_size >= 8) {
      if (dacp_tlv_crawl(&sp, &item_size) == 'cmgt') {
        sp -= item_size; // drop down into the array -- don't skip over it
        reply_size -= 8;
        while (reply_size >= 8) {
          uint32_t type = dacp_tlv_crawl(&sp, &item_size);
          reply_size -= item_size + 8;
          if (type == 'cmvo') { // drop down into the dictionary -- don't skip over it
            char *t = sp - item_size;
            overall_volume = ntohl(*(uint32_t *)(t));
          }
        }
      } else {
        debug(1, "Unexpected payload response from getproperty?properties=dmcp.volume");
      }
    } else {
      debug(1, "Too short a response from getproperty?properties=dmcp.volume");
    }
    // debug(1, "Overall Volume is %d.", overall_volume);
  }

  if (server_reply) {
    // debug(1, "Freeing response memory.");
    free(server_reply);
    server_reply = NULL;
  }

  if (result) {
    *result = overall_volume;
    // debug(1,"dacp_get_client_volume returns: %" PRId32 ".",overall_volume);
  }
  return response;
}

int dacp_set_include_speaker_volume(int64_t machine_number, int32_t vo) {
  debug(2, "dacp_set_include_speaker_volume to %" PRId32 ".", vo);
  char message[1000];
  memset(message, 0, sizeof(message));
  snprintf(message, sizeof(message),
           "setproperty?include-speaker-id=%" PRId64 "&dmcp.volume=%" PRId32 "", machine_number,
           vo);
  debug(2, "sending \"%s\"", message);
  return send_simple_dacp_command(message);
  // should return 204
}

int dacp_set_speaker_volume(int64_t machine_number, int32_t vo) {
  char message[1000];
  memset(message, 0, sizeof(message));
  snprintf(message, sizeof(message), "setproperty?speaker-id=%" PRId64 "&dmcp.volume=%" PRId32 "",
           machine_number, vo);
  debug(2, "sending \"%s\"", message);
  return send_simple_dacp_command(message);
  // should return 204
}

int dacp_get_speaker_list(dacp_spkr_stuff *speaker_info, int max_size_of_array,
                          int *actual_speaker_count) {
  // char typestring[5];
  char *server_reply = NULL;
  int speaker_index = -1; // will be incremented before use
  int speaker_count = -1; // will be fixed if there is no problem
  ssize_t le;

  // debug(1,"dacp_speaker_list: dacp_send_command");
  int response = dacp_send_command("getspeakers", &server_reply, &le);
  if (response == 200) {
    char *sp = server_reply;
    int32_t item_size;
    if (le >= 8) {
      if (dacp_tlv_crawl(&sp, &item_size) == 'casp') {
        //          debug(1,"Speakers:",item_size);
        sp -= item_size; // drop down into the array -- don't skip over it
        le -= 8;
        while (le >= 8) {
          uint32_t type = dacp_tlv_crawl(&sp, &item_size);
          if (type == 'mdcl') { // drop down into the dictionary -- don't skip over it
            // debug(1,">>>> Dictionary:");
            sp -= item_size;
            le -= 8;
            speaker_index++;
            if (speaker_index == max_size_of_array) {
              return 413; // Payload Too Large -- too many speakers
            }
            speaker_info[speaker_index].active = 0;
            speaker_info[speaker_index].speaker_number = 0;
            speaker_info[speaker_index].volume = 0;
            speaker_info[speaker_index].name[0] = '\0';
          } else {
            le -= item_size + 8;
            char *t;
            // char u;
            int32_t r;
            int64_t s, v;
            switch (type) {
            case 'minm':
              t = sp - item_size;
              strncpy((char *)&speaker_info[speaker_index].name, t,
                      sizeof(speaker_info[speaker_index].name));
              speaker_info[speaker_index].name[sizeof(speaker_info[speaker_index].name) - 1] =
                  '\0'; // just in case
              break;
            case 'cmvo':
              t = sp - item_size;
              r = ntohl(*(uint32_t *)(t));
              speaker_info[speaker_index].volume = r;
              // debug(1,"The individual volume of speaker \"%s\" is
              // \"%d\".",speaker_info[speaker_index].name,r);
              break;
            case 'msma':
              t = sp - item_size;
              s = ntohl(*(uint32_t *)(t));
              s = s << 32;
              t += 4;
              v = (ntohl(*(uint32_t *)(t))) & 0xffffffff;
              s += v;
              speaker_info[speaker_index].speaker_number = s;
              // debug(1,"Speaker machine number: %ld",s);
              break;

            case 'caia':
              speaker_info[speaker_index].active = 1;
              break;
            /*
                            case 'caip':
                            case 'cavd':
                            case 'caiv':
                            case 'cads':

                              *(uint32_t *)typestring = htonl(type);
                              typestring[4] = 0;



                              t = sp-item_size;
                              u = *t;
                              debug(1,"Type: '%s' Value: \"%d\".",typestring,u);
                              break;
            */
            default:
              break;
            }
          }
        }
        // debug(1,"Total of %d speakers found. Here are the active ones:",speaker_index+1);
        speaker_count = speaker_index + 1; // number of speaker entries in the array
      } else {
        debug(1, "Speaker array not found.");
      }
      /*
              int i;
              for (i=0;i<le;i++) {
                if (*sp < ' ')
                  debug(1,"%d  %02x", i, *sp);
                else
                  debug(1,"%d  %02x  '%c'", i, *sp,*sp);
                sp++;
              }
      */
    } else {
      debug(1, "Can't find any content in dacp speakers request");
    }
    free(server_reply);
    server_reply = NULL;
  } else {
    // debug(1, "Unexpected response %d to dacp speakers request", response);
    if (server_reply) {
      debug(1, "Freeing response memory.");
      free(server_reply);
      server_reply = NULL;
    }
  }
  if (actual_speaker_count)
    *actual_speaker_count = speaker_count;
  return response;
}

int dacp_get_volume(int32_t *the_actual_volume) {
  // get the speaker volume information from the DACP source and store it in the metadata_hub
  // A volume command has been sent from the client
  // let's get the master volume from the DACP remote control
  struct dacp_speaker_stuff speaker_info[50];
  // we need the overall volume and the speakers information to get this device's relative volume to
  // calculate the real volume

  int32_t overall_volume = 0;
  int32_t actual_volume = 0;
  int http_response = dacp_get_client_volume(&overall_volume);
  if (http_response == 200) {
    // debug(1,"Overall volume is: %u.",overall_volume);
    int speaker_count = 0;
    http_response = dacp_get_speaker_list((dacp_spkr_stuff *)&speaker_info, 50, &speaker_count);
    if (http_response == 200) {
      // get our machine number
      uint16_t *hn = (uint16_t *)config.hw_addr;
      uint32_t *ln = (uint32_t *)(config.hw_addr + 2);
      uint64_t t1 = ntohs(*hn);
      uint64_t t2 = ntohl(*ln);
      int64_t machine_number = (t1 << 32) + t2; // this form is useful

      // Let's find our own speaker in the array and pick up its relative volume
      int i;
      int32_t relative_volume = 0;
      for (i = 0; i < speaker_count; i++) {
        if (speaker_info[i].speaker_number == machine_number) {
          relative_volume = speaker_info[i].volume;
          /*
          debug(1,"Our speaker was found with a relative volume of: %u.",relative_volume);

          if (speaker_info[i].active)
            debug(1,"Our speaker is active.");
          else
            debug(1,"Our speaker is inactive.");
          */
        }
      }
      actual_volume = (overall_volume * relative_volume + 50) / 100;
      // debug(1,"Overall volume: %d, relative volume: %d%, actual volume:
      // %d.",overall_volume,relative_volume,actual_volume);
      // debug(1,"Our actual speaker volume is %d.",actual_volume);
      // metadata_hub_modify_prolog();
      // metadata_store.speaker_volume = actual_volume;
      // metadata_hub_modify_epilog(1);
    } else {
      debug(2, "Unexpected return code %d from dacp_get_speaker_list.", http_response);
    }
  } else if ((http_response != 400) && (http_response != 490)) {
    debug(3, "Unexpected return code %d from dacp_get_client_volume.", http_response);
  }
  if (the_actual_volume) {
    // debug(1,"dacp_get_volume returns %d.",actual_volume);
    *the_actual_volume = actual_volume;
  }
  return http_response;
}

int dacp_set_volume(int32_t vo) {
  int http_response = 492; // argument out of range
  if ((vo >= 0) && (vo <= 100)) {
    // get the information we need -- the absolute volume, the speaker list, our ID
    struct dacp_speaker_stuff speaker_info[50];
    int32_t overall_volume;
    http_response = dacp_get_client_volume(&overall_volume);
    if (http_response == 200) {
      int speaker_count;
      http_response = dacp_get_speaker_list((dacp_spkr_stuff *)&speaker_info, 50, &speaker_count);
      if (http_response == 200) {
        // get our machine number
        uint16_t *hn = (uint16_t *)config.hw_addr;
        uint32_t *ln = (uint32_t *)(config.hw_addr + 2);
        uint64_t t1 = ntohs(*hn);
        uint64_t t2 = ntohl(*ln);
        int64_t machine_number = (t1 << 32) + t2; // this form is useful

        // Let's find our own speaker in the array and pick up its relative volume
        int i;
        int32_t active_speakers = 0;
        for (i = 0; i < speaker_count; i++) {
          if (speaker_info[i].speaker_number == machine_number) {
            debug(2, "Our speaker number found: %ld with relative volume.", machine_number,
                  speaker_info[i].volume);
          }
          if (speaker_info[i].active == 1) {
            active_speakers++;
          }
        }

        if (active_speakers == 1) {
          // must be just this speaker
          debug(2, "Remote-setting volume to %d on just one speaker.", vo);
          http_response = dacp_set_include_speaker_volume(machine_number, vo);
        } else if (active_speakers == 0) {
          debug(2, "No speakers!");
        } else {
          debug(2, "Speakers: %d, active: %d", speaker_count, active_speakers);
          if (vo >= overall_volume) {
            debug(2, "Multiple speakers active, but desired new volume is highest");
            http_response = dacp_set_include_speaker_volume(machine_number, vo);
          } else {
            // the desired volume is less than the current overall volume and there is more than
            // one
            // speaker
            // we must find out the highest other speaker volume.
            // If the desired volume is less than it, we must set the current_overall volume to
            // that
            // highest volume
            // and set our volume relative to it.
            // If the desired volume is greater than the highest current volume, then we can just
            // go
            // ahead
            // with dacp_set_include_speaker_volume, setting the new current overall volume to the
            // desired new level
            // with the speaker at 100%

            int32_t highest_other_volume = 0;
            for (i = 0; i < speaker_count; i++) {
              if ((speaker_info[i].speaker_number != machine_number) &&
                  (speaker_info[i].active == 1) &&
                  (speaker_info[i].volume > highest_other_volume)) {
                highest_other_volume = speaker_info[i].volume;
              }
            }
            highest_other_volume = (highest_other_volume * overall_volume + 50) / 100;
            if (highest_other_volume <= vo) {
              debug(2,
                    "Highest other volume %d is less than or equal to the desired new volume %d.",
                    highest_other_volume, vo);
              http_response = dacp_set_include_speaker_volume(machine_number, vo);
            } else {
              debug(2, "Highest other volume %d is greater than the desired new volume %d.",
                    highest_other_volume, vo);
              // if the present overall volume is higher than the highest other volume at present,
              // then bring it down to it.
              if (overall_volume > highest_other_volume) {
                debug(2, "Lower overall volume to new highest volume.");
                http_response = dacp_set_include_speaker_volume(
                    machine_number,
                    highest_other_volume); // set the overall volume to the highest one
              }
              int32_t desired_relative_volume =
                  (vo * 100 + (highest_other_volume / 2)) / highest_other_volume;
              debug(2, "Set our speaker volume relative to the highest volume.");
              http_response = dacp_set_speaker_volume(
                  machine_number,
                  desired_relative_volume); // set the overall volume to the highest one
            }
          }
        }
      } else {
        debug(2, "Can't get speakers list");
      }
    } else {
      debug(2, "Can't get client volume");
    }

  } else {
    debug(2, "Invalid volume: %d -- ignored.", vo);
  }
  return http_response;
}
