/****************************************************************************
 * apps/examples/tcp_ipc_server/tcp_ipc_server_main.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/* This program consists of a server socket & custom messages to establish
 * IPC for multiple applications (clients) and one process that controls
 * LoRaWAN connectivity (server). Both client and server work on local
 * network.
 * For more details about client side, see client-tcp example.
 *
 * IMPORTANT NOTE:
 * In order to test client_tcp & server_tcp together, there are two
 * ways to proceed:
 * 1) Init server manually (command: SERVER &), and after successfull
 * server init, also init client manually (CLIENT 127.0.0.1)
 * 2) init server automatically after boot using NuttShell start up scripts.
 */

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>

#include "protocol.h"
#include "lorawan/uart_lorawan_layer.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/****************************************************************************
 * Private Types
 ****************************************************************************/

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/****************************************************************************
 * Private Data
 ****************************************************************************/

static int socket_clients_counter;

/****************************************************************************
 * Public Data
 ****************************************************************************/

/****************************************************************************
 * Private Functions
 ****************************************************************************/

static void *thread_socket_client(void *arg);

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Definitions
 ****************************************************************************/

#define SOCKET_PORT                         5000
#define TX_BUFFER_SIZE                      200
#define RX_BUFFER_SIZE                      150
#define TCP_NO_FLAGS_WHEN_SENDING_DATA      0
#define IP_SERVER                           "127.0.0.1"
#define MAX_PENDING_SOCKET_CONNECTIONS      20
#define TIME_TO_CHECK_FOR_NEW_CLIENTS       500000

/****************************************************************************
 * Name: thread_socket_client
 * Description: socket client thread. Each sonnected cleint socket will
 *              instantiate a thread, which handles all data traffic between
 *              this client and server.
 * Parameters: thread arguments (in this case, file descriptor of client
 *             socket)
 * Return: nothing
 ****************************************************************************/

static void *thread_socket_client(void *arg)
{
  int socket_client_fd = *((int *)arg);
  char tx_buffer[TX_BUFFER_SIZE];
  unsigned char rx_buffer[RX_BUFFER_SIZE];
  int bytes_read_from_server = 0;
  int ret_send = 0;
  protocolo_ipc tprotocol;

  memset(tx_buffer, 0x00, sizeof(tx_buffer));
  memset(rx_buffer, 0x00, sizeof(rx_buffer));

  while (1)
    {
      bytes_read_from_server = read(socket_client_fd,
                                    rx_buffer,
                                    RX_BUFFER_SIZE);

      if (bytes_read_from_server == 0)
        {
          /* Socket disconnection has been detected.
           * This thread will be terminated.
           */

          printf("\n\rDisconnection has been detected.\n\r");
          break;
        }
      else
        {
          printf("Client request received! Comm. with client...\n");
          send_msg_to_lpwan (rx_buffer, &tprotocol);
          if (tprotocol.msg_size >= 0)
            {
              memcpy(tx_buffer,
                     (unsigned char *)&tprotocol,
                     sizeof(protocolo_ipc));
              ret_send = send(socket_client_fd,
                              tx_buffer,
                              sizeof(protocolo_ipc),
                              TCP_NO_FLAGS_WHEN_SENDING_DATA);

              if (ret_send > 0)
                {
                  printf("\r\nSucess: %d bytes sent to client\r\n",
                         ret_send);
                }
              else
                {
                  printf("\r\nError: fail to send %d bytes to client\r\n",
                         strlen(tx_buffer));
                }
            }
          else
            {
                printf("ERROR: invalid message size (<0)\n");
            }
        }
    }

  /* Terminate this thread */

  close(socket_client_fd);
  socket_clients_counter--;
  pthread_exit(NULL);
}

/****************************************************************************
 * Server_tcp_main
 ****************************************************************************/

int main(int argc, char *argv[])
{
  int socket_server_fd = 0;
  int new_socket_client_fd = 0;
  int ret_bind = 0;
  struct sockaddr_in server_add;
  struct sockaddr_storage server_storage;
  socklen_t addr_size;
  pthread_t tid[MAX_PENDING_SOCKET_CONNECTIONS];
  config_lorawan_radioenge_t lora_config;

  socket_clients_counter = 0;

  /* Configuring LoRaWAN credentials */

  snprintf(lora_config.application_session_key, APP_SESSION_KEY_SIZE,
           "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00");
  snprintf(lora_config.network_session_key, NW_SESSION_KEY_SIZE,
           "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00");
  snprintf(lora_config.application_eui, APP_EUI_SIZE,
           "00:00:00:00:00:00:00:00");
  snprintf(lora_config.device_address, DEVICE_ADDRESS_SIZE,
           "00:00:00:00");
  snprintf(lora_config.channel_mask, CHANNEL_MASK_SIZE,
           "00FF:0000:0000:0000:0000:0000");
  lorawan_radioenge_init(lora_config);

  /* Create socket (server) */

  socket_server_fd = socket(PF_INET, SOCK_STREAM, 0);
  if (socket_server_fd == 0)
    {
      perror("Failed to create server socket");
      exit(EXIT_FAILURE);
    }

  /* Fill addr structure and do socket bind operation */

  server_add.sin_family = AF_INET;
  server_add.sin_port = htons(SOCKET_PORT);
  server_add.sin_addr.s_addr = inet_addr(IP_SERVER);
  memset(server_add.sin_zero, '\0', sizeof server_add.sin_zero);

  ret_bind = bind(socket_server_fd, (struct sockaddr *)&server_add,
                  sizeof(server_add));
  if (ret_bind < 0)
    {
      perror("Failed to do socket bind operation");
      exit(EXIT_FAILURE);
    }

  /* Initiate listening process for client sockets connection requests.
   * The maximum number client sockets pending connection is
   * defined by MAX_PENDING_SOCKET_CONNECTIONS
   */

  if (listen(socket_server_fd, MAX_PENDING_SOCKET_CONNECTIONS) == 0)
    {
      printf("Listening for clients connections...\n");
    }
  else
    {
      perror("Failed to listen for clients connections");
    }

  while (1)
    {
      /* Wait for a new client socket connection */

      addr_size = sizeof server_storage;
      new_socket_client_fd = accept(socket_server_fd,
                                    (struct sockaddr *)&server_storage,
                                    &addr_size);

      if (new_socket_client_fd < 0)
        {
          perror("Failed to accept new client socket connection");
          exit(EXIT_FAILURE);
        }

      /* For each connected client socket, a new client thread
       * is instantiated.
       */

      if (pthread_create(&tid[socket_clients_counter++], NULL,
                        thread_socket_client, &new_socket_client_fd) != 0)
        {
          perror("Failed to instantiate a thread for new client socket\n");
        }

      if (socket_clients_counter <= MAX_PENDING_SOCKET_CONNECTIONS)
        {
          pthread_join(tid[socket_clients_counter++], NULL);
          socket_clients_counter++;
        }

      usleep(TIME_TO_CHECK_FOR_NEW_CLIENTS);
    }

  return 0;
}