#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include <unistd.h>
#include <getopt.h>
#include <sys/time.h>

#include <endian.h>
#include <sys/timerfd.h>

#include "Version.h"
#include "RewindClient.h"

#define TDMA_FRAME_DURATION  60

#define DSD_MAGIC_TEXT        ".amb"
#define DSD_MAGIC_SIZE        4
#define DSD_AMBE_CHUNK_SIZE   8

#define HELPER(value)         #value
#define STRING(value)         HELPER(value)

#define COUNT(array)          sizeof(array) / sizeof(array[0])

#define BUFFER_SIZE           1024
#define ATTEMPT_COUNT         5

#define CLIENT_NAME           "DigestPlay " STRING(VERSION) " " BUILD

typedef uint8_t Integer24[3];

struct FullLC
{
  uint8_t code;
  uint8_t feature;
  uint8_t options;
  Integer24 destination;
  Integer24 source;
  Integer24 sum;
};

void EncodeInteger24(uint32_t value, Integer24 data)
{
  data[0] = (value >> 16) & 0xff;
  data[1] = (value >>  8) & 0xff;
  data[2] =  value        & 0xff;
}

int main(int argc, char* argv[])
{
  printf("\n");
  printf("DigestPlay for BrandMeister DMR Master Server\n");
  printf("Copyright 2017 Artem Prilutskiy (R3ABM, cyanide.burnout@gmail.com)\n");
  printf("Software revision " STRING(VERSION) " build " BUILD "\n");
  printf("\n");

  // Main variables

  uint32_t number = 0;
  const char* port = "54005";
  const char* location = NULL;
  const char* password = NULL;

  struct FullLC header;
  memset(&header, 0, sizeof(struct FullLC));

  // Start up

  struct option options[] =
  {
    { "client-password",  required_argument, NULL, 'w' },
    { "client-number",    required_argument, NULL, 'c' },
    { "server-address",   required_argument, NULL, 's' },
    { "server-port",      required_argument, NULL, 'p' },
    { "source-id",        required_argument, NULL, 'i' },
    { "group-id",         required_argument, NULL, 'g' },
    { NULL,               0,                 NULL, 0   }
  };

  int value = 0;
  int control = 0;
  int selection = 0;

  while ((selection = getopt_long(argc, argv, "w:c:s:p:i:g:", options, NULL)) != EOF)
    switch (selection)
    {
      case 'w':
        password = optarg;
        control |= 0b00001;
        break;

      case 's':
        location = optarg;
        control |= 0b00010;
        break;

      case 'p':
        port = optarg;
        break;

      case 'c':
        number = strtol(optarg, NULL, 10);
        control |= 0b00100;
        break;

      case 'i':
        value = strtol(optarg, NULL, 10);
        if (value > 0)
        {
          EncodeInteger24(value, header.source);
          control |= 0b01000;
        }
        break;

      case 'g':
        value = strtol(optarg, NULL, 10);
        if (value > 0)
        {
          EncodeInteger24(value, header.destination);
          control |= 0b10000;
        }
        break;
    }

  if (control != 0b11111)
  {
    printf(
      "Usage:\n"
      "  %s\n"
      "    --client-number <Registered ID of client>\n"
      "    --client-password <access password for BrandMeister DMR Server>\n"
      "    --server-address <domain name of BrandMeister DMR Server>\n"
      "    --server-port <service port for BrandMeister DMR Server>\n"
      "    --source-id <ID to use as a source>\n"
      "    --group-id <TG ID>\n"
      "\n",
      argv[0]);
    return EXIT_FAILURE;
  }

  // Create Rewind client context

  struct RewindContext* context = CreateRewindClient(number, CLIENT_NAME);

  if (context == NULL)
  {
    printf("Error creating context\n");
    return EXIT_FAILURE;
  }

  // Check input data format

  char* buffer = (char*)alloca(BUFFER_SIZE);
  ssize_t length = read(STDIN_FILENO, buffer, DSD_MAGIC_SIZE);

  if ((length != DSD_MAGIC_SIZE) ||
      (memcmp(buffer, DSD_MAGIC_TEXT, length) != 0))
  {
    printf("Error checking input data format\n");
    ReleaseRewindClient(context);
    return EXIT_FAILURE;
  }

  // Connect to the server

  int result = ConnectRewindClient(context, location, port, password, REWIND_OPTION_LINEAR_FRAME);

  if (result < 0)
  {
    printf("Cannot connect to the server (%i)\n", result);
    ReleaseRewindClient(context);
    return EXIT_FAILURE;
  }

  // Initialize timer handle

  int handle;
  struct itimerspec interval;

  memset(&interval, 0, sizeof(struct itimerspec));

  interval.it_interval.tv_sec = 0;
  interval.it_interval.tv_nsec = TDMA_FRAME_DURATION * 1000000;

  handle = timerfd_create(CLOCK_MONOTONIC, 0);
  timerfd_settime(handle, 0, &interval, NULL);

  // Transmit voice header

  printf("Playing...\n");

  TransmitRewindData(context, REWIND_TYPE_DMR_DATA_BASE + 1, REWIND_FLAG_REAL_TIME_1, &header, sizeof(struct FullLC));
  TransmitRewindData(context, REWIND_TYPE_DMR_DATA_BASE + 1, REWIND_FLAG_REAL_TIME_1, &header, sizeof(struct FullLC));
  TransmitRewindData(context, REWIND_TYPE_DMR_DATA_BASE + 1, REWIND_FLAG_REAL_TIME_1, &header, sizeof(struct FullLC));
 
  // Main loop

  uint64_t mark;
  size_t count = 0;

  // Wait for timer event (60 milliseconds)
  while (read(handle, &mark, sizeof(uint64_t)) > 0)
  {
    ssize_t length1 = read(STDIN_FILENO, buffer + 0 * DSD_AMBE_CHUNK_SIZE, DSD_AMBE_CHUNK_SIZE);
    ssize_t length2 = read(STDIN_FILENO, buffer + 1 * DSD_AMBE_CHUNK_SIZE, DSD_AMBE_CHUNK_SIZE);
    ssize_t length3 = read(STDIN_FILENO, buffer + 2 * DSD_AMBE_CHUNK_SIZE, DSD_AMBE_CHUNK_SIZE);

    if ((length1 != DSD_AMBE_CHUNK_SIZE) ||
        (length2 != DSD_AMBE_CHUNK_SIZE) ||
        (length3 != DSD_AMBE_CHUNK_SIZE))
    {
      printf("Input data stream stopped\n");
      break;
    }

    buffer[0 * DSD_AMBE_CHUNK_SIZE + 7] <<= 7;
    buffer[1 * DSD_AMBE_CHUNK_SIZE + 7] <<= 7;
    buffer[2 * DSD_AMBE_CHUNK_SIZE + 7] <<= 7;

    TransmitRewindData(context, REWIND_TYPE_DMR_AUDIO_FRAME, REWIND_FLAG_REAL_TIME_1, buffer, 3 * DSD_AMBE_CHUNK_SIZE);

    if ((count % 83) == 0)
    {
      // Every 5 seconds of transmission
      TransmitRewindKeepAlive(context);
    }

    count ++;
  }

  // Clean up

  printf("Done\n");

  TransmitRewindCloae(context);
  ReleaseRewindClient(context);

  return EXIT_SUCCESS;
};