// Copyright 2015-2016 by Artem Prilutskiy

#include "PatchCord.h"
#include <stdlib.h>
#include <string.h>

#include <syslog.h>
#include <stdio.h>

#define SERVICE_NAME    "me.burnaway.BrandMeister"
#define OBJECT_PATH     "/me/burnaway/BrandMeister"
#define INTERFACE_NAME  "me.burnaway.BrandMeister"

// From AutoPatch.cpp
#define AUTOPATCH_LINK_NAME  "AutoPatch"

// From PatchCord.h
#define VALUE_CORD_OUTGOING_SOURCE_ID     1
#define VALUE_CORD_INCOMING_SOURCE_ID     4
#define VALUE_CORD_TALKER_ALIAS           7

PatchCord::PatchCord(uint32_t network, uint32_t link)
{
  banner = NULL;
  number = link;
  asprintf(&name, SERVICE_NAME ".N%d", network);
  connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
}

PatchCord::~PatchCord()
{
  dbus_connection_unref(connection);
  free(banner);
  free(name);
}

void PatchCord::setTalkerID(uint32_t value)
{
  getContextBanner();
  setSpecificValue(VALUE_CORD_OUTGOING_SOURCE_ID, value);
}

uint32_t PatchCord::getTalkerID()
{
  getContextBanner();
  return getSpecificValue(VALUE_CORD_INCOMING_SOURCE_ID);
}

void PatchCord::setTalkerAlias(const char* value)
{
  char* buffer;
  asprintf(&buffer, "set alias %s", value);

  getContextBanner();
  invokeCommand(buffer);

  free(buffer);
}

void PatchCord::getContextBanner()
{
  DBusMessage* message = dbus_message_new_method_call(
    name, OBJECT_PATH, INTERFACE_NAME, "getContextList");

  const char* name = AUTOPATCH_LINK_NAME;

  dbus_message_append_args(message,
    DBUS_TYPE_STRING, &name,
    DBUS_TYPE_UINT32, &number,
    DBUS_TYPE_INVALID);

  DBusPendingCall* pending;
  if (dbus_connection_send_with_reply(connection, message, &pending, -1))
  {
    dbus_connection_flush(connection);
    dbus_message_unref(message);
    dbus_pending_call_block(pending);
    message = dbus_pending_call_steal_reply(pending);
    char** array;
    int count;
    if ((dbus_message_get_args(message, NULL,
          DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &array, &count,
          DBUS_TYPE_INVALID)) &&
        (count > 0))
    {
      free(banner);
      banner = strdup(*array);
      dbus_free_string_array(array);
    }
    dbus_pending_call_unref(pending);
  }
  dbus_message_unref(message);
}

void PatchCord::invokeCommand(const char* command)
{
  DBusMessage* message = dbus_message_new_method_call(
    name, OBJECT_PATH, INTERFACE_NAME, "invokeCommand");

  dbus_message_append_args(message,
    DBUS_TYPE_STRING, &banner,
    DBUS_TYPE_STRING, &command,
    DBUS_TYPE_INVALID);
  
  DBusPendingCall* pending;
  if (dbus_connection_send_with_reply(connection, message, &pending, -1))
  {
    dbus_connection_flush(connection);
    dbus_message_unref(message);
    dbus_pending_call_block(pending);
    message = dbus_pending_call_steal_reply(pending);
    dbus_pending_call_unref(pending);
  }
  dbus_message_unref(message);
  
  // Each call of dbus_message_unref removes banner from the heap
  banner = NULL;
}

void PatchCord::setSpecificValue(uint32_t key, uint32_t value)
{
  DBusMessage* message = dbus_message_new_method_call(
    name, OBJECT_PATH, INTERFACE_NAME, "setSpecificValue");

  dbus_message_append_args(message,
    DBUS_TYPE_STRING, &banner,
    DBUS_TYPE_UINT32, &key,
    DBUS_TYPE_UINT32, &value,
    DBUS_TYPE_INVALID);
  
  DBusPendingCall* pending;
  if (dbus_connection_send_with_reply(connection, message, &pending, -1))
  {
    dbus_connection_flush(connection);
    dbus_message_unref(message);
    dbus_pending_call_block(pending);
    message = dbus_pending_call_steal_reply(pending);
    dbus_pending_call_unref(pending);
  }
  dbus_message_unref(message);
  
  // Each call of dbus_message_unref removes banner from the heap
  banner = NULL;
}

uint32_t PatchCord::getSpecificValue(uint32_t key)
{
  DBusMessage* message = dbus_message_new_method_call(
    name, OBJECT_PATH, INTERFACE_NAME, "getContextData");

  dbus_message_append_args(message,
    DBUS_TYPE_STRING, &banner,
    DBUS_TYPE_INVALID);

  uint32_t value = 0;

  DBusPendingCall* pending;
  if (dbus_connection_send_with_reply(connection, message, &pending, -1))
  {
    dbus_connection_flush(connection);
    dbus_message_unref(message);
    dbus_pending_call_block(pending);

    message = dbus_pending_call_steal_reply(pending);

    const char* name;
    const char* address;
    dbus_uint32_t type;
    dbus_uint32_t state;
    dbus_uint32_t number;
    dbus_uint32_t* values;
    int count;

    if (dbus_message_get_args(message, NULL,
          DBUS_TYPE_STRING, &banner,
          DBUS_TYPE_STRING, &name,
          DBUS_TYPE_UINT32, &type,
          DBUS_TYPE_UINT32, &number,
          DBUS_TYPE_STRING, &address,
          DBUS_TYPE_UINT32, &state,
          DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &values, &count,
          DBUS_TYPE_INVALID))
      value = values[key];

    dbus_pending_call_unref(pending);
  }

  dbus_message_unref(message);

  // Each call of dbus_message_unref removes banner from the heap
  banner = NULL;

  return value;
}

uint32_t PatchCord::getPrivateIDForCall(const char* call)
{
  DBusMessage* message = dbus_message_new_method_call(
    name, OBJECT_PATH, INTERFACE_NAME, "getStationData");

  dbus_message_append_args(message,
    DBUS_TYPE_STRING, &call,
    DBUS_TYPE_INVALID);

  uint32_t value = 0;

  DBusPendingCall* pending;
  if (dbus_connection_send_with_reply(connection, message, &pending, -1))
  {
    dbus_connection_flush(connection);
    dbus_message_unref(message);
    dbus_pending_call_block(pending);

    message = dbus_pending_call_steal_reply(pending);

    dbus_uint32_t number;
    dbus_uint32_t algorithm;
    dbus_uint32_t key;
    dbus_uint32_t interval;
    dbus_uint32_t capabilities;
    dbus_uint32_t station;
    const char* language;
    const char* call;
    const char* text;
    const char* symbol;

    if (dbus_message_get_args(message, NULL,
          DBUS_TYPE_UINT32, &number,
          DBUS_TYPE_UINT32, &algorithm,
          DBUS_TYPE_UINT32, &key,
          DBUS_TYPE_UINT32, &interval,
          DBUS_TYPE_UINT32, &capabilities,
          DBUS_TYPE_STRING, &language,
          DBUS_TYPE_UINT32, &station,
          DBUS_TYPE_STRING, &call,
          DBUS_TYPE_STRING, &text,
          DBUS_TYPE_STRING, &symbol,
          DBUS_TYPE_INVALID))
      value = number;

    dbus_pending_call_unref(pending);
  }

  dbus_message_unref(message);

  return value;
}

bool PatchCord::getCredentialsForID(uint32_t number, char* call, char* text)
{
  DBusMessage* message = dbus_message_new_method_call(
    name, OBJECT_PATH, INTERFACE_NAME, "getStationData");

  dbus_message_append_args(message,
    DBUS_TYPE_UINT32, &number,
    DBUS_TYPE_INVALID);

  bool result = false;

  DBusPendingCall* pending;
  if (dbus_connection_send_with_reply(connection, message, &pending, -1))
  {
    dbus_connection_flush(connection);
    dbus_message_unref(message);
    dbus_pending_call_block(pending);

    message = dbus_pending_call_steal_reply(pending);

    dbus_uint32_t number;
    dbus_uint32_t algorithm;
    dbus_uint32_t key;
    dbus_uint32_t interval;
    dbus_uint32_t capabilities;
    dbus_uint32_t station;
    const char* language;
    const char* value1;
    const char* value2;
    const char* symbol;

    if (dbus_message_get_args(message, NULL,
          DBUS_TYPE_UINT32, &number,
          DBUS_TYPE_UINT32, &algorithm,
          DBUS_TYPE_UINT32, &key,
          DBUS_TYPE_UINT32, &interval,
          DBUS_TYPE_UINT32, &capabilities,
          DBUS_TYPE_STRING, &language,
          DBUS_TYPE_UINT32, &station,
          DBUS_TYPE_STRING, &value1,
          DBUS_TYPE_STRING, &value2,
          DBUS_TYPE_STRING, &symbol,
          DBUS_TYPE_INVALID))
    {
      strcpy(call, value1);
      strcpy(text, value2);
      result = true;
    }

    dbus_pending_call_unref(pending);
  }

  dbus_message_unref(message);

  return result;
}