////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
// All Rights Reserved.
// Confidential and Proprietary - Qualcomm Technologies, Inc.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// THIS IS AUTO-GENERATED CODE.  PLEASE DO NOT EDIT (File bug reports against tools).
///
/// Auto-generated by: Parameter Parser V5.5.1 (2411131018)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @file  parametereztunegateway.cpp
/// @brief Auto-generated Chromatix parameter file
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "parameterutils.h"
#include "parametereztunecombase.h"
#include "parametereztunegateway.h"
#include "parametereztunecom.h"

#ifdef _WIN32
#include <conio.h>

#undef UNICODE
#define _WINSOCKAPI_

#include <windows.h>
#include <winsock2.h>
#include <Ws2tcpip.h>

typedef int SOCKET_LEN;

#define PARAMETER_INVALID_SOCKET INVALID_SOCKET

// Need to link with Ws2_32.lib
#pragma comment (lib, "Ws2_32.lib")

#else
// #include <curses.h> not available on our build machines application must be killed on Linux
#include <cstdio>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <poll.h>

// Supress warning: swab.h:104:29: error: unused function '__swab32s' [-Werror,-Wunused-function]
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
#include <netinet/in.h>
#pragma GCC diagnostic pop

typedef socklen_t SOCKET_LEN;

#define PARAMETER_INVALID_SOCKET -1

#endif // _WIN32

INT32 main(INT32 argc, CHAR* argv[])
{
    EzTuneGateway gateway;

    UINT8 maxPorts = EzTuneGateway::MAX_PORTS;
    if (argc == 2)
    {
        double value = ParameterUtils::StringToDouble(argv[1]);
        if (value >= 1 && value <= 256) // Allowed ports must be between 1 and MAX_UINT8 (256)
        {
            maxPorts = (UINT8)value;
        }
        else
        {
            PARAMETER_LOG_ERROR(nullptr, "Parameter EzTune Gateway invalid Max Ports value: %s, between 1 and 256 ports are allowed, using %u\n", argv[1], maxPorts);
        }
    }
    else if (argc != 1)
    {
        PARAMETER_LOG_ERROR(nullptr, "Parameter EzTune Gateway invalid parameter count (%d) a maximum of one parameter (Max Ports) is allowed\n", argc);
    }
    gateway.Start(maxPorts);

#ifdef _WIN32
    PARAMETER_LOG_DEBUG(nullptr, "Parameter EzTune Gateway (Build ID: %s) running, Max Ports = %u, press any key to exit\n", EzTuneGateway::GATEWAY_BUILDID, maxPorts);
    (void)_getch();
#else
    // curses.h is not available on our Linux build machines application must be killed on Linux
    PARAMETER_LOG_DEBUG(nullptr, "Parameter EzTune Gateway (Build ID: %s) running, kill process to exit\n", EzTuneGateway::GATEWAY_BUILDID);
    gateway.ExitEvent.Wait(EzTuneEvent::INDEFINITE);
#endif

    gateway.Stop();

    PARAMETER_LOG_DEBUG(nullptr, "Parameter EzTune Gateway exiting\n");
}

const CHAR* EzTuneGateway::GATEWAY_BUILDID = "2411131018";

EzTuneGateway::EzTuneGateway()
{
    m_maxPorts    = MAX_PORTS;
    m_startThread = nullptr;
#ifdef _WIN32
    WSADATA wsaData;
    INT32 WSAresult = WSAStartup(MAKEWORD(2, 2), &wsaData);

    if (WSAresult != NO_ERROR)
    {
        PARAMETER_LOG_ERROR(nullptr, "OpenSocket failed WSAStartup error: %d\n", WSAresult);
    }
#endif // _WIN32
}

EzTuneGateway::~EzTuneGateway()
{
    Stop();

#ifdef _WIN32
    WSACleanup();
#endif
}

void EzTuneGateway::Start(const UINT8 maxPorts)
{
    m_maxPorts = maxPorts;
    m_workerMutex.lock();
    if (m_startThread == nullptr)
    {
        PARAMETER_LOG_DEBUG(nullptr, "Parameter EzTune Gateway (Build ID: %s) starting\n", GATEWAY_BUILDID);
        m_startThread = new std::thread(&EzTuneGateway::StartWorkerThread, this);
    }
    m_workerMutex.unlock();
}

void EzTuneGateway::Stop()
{
    if (m_startThread != nullptr)
    {
        PARAMETER_LOG_DEBUG(nullptr, "Parameter EzTune Gateway (Build ID: %s) stopping\n", GATEWAY_BUILDID);
        ExitEvent.Set();

        m_workerMutex.lock();
        for (UINT32 i = 0; i < m_workers.Count(); i++)
        {
            m_workers[i].Thread->join();
            PARAMETER_DELETE m_workers[i].Info;
            PARAMETER_DELETE m_workers[i].Thread;
        }
        m_startThread->join();
        PARAMETER_DELETE m_startThread;
        m_startThread = nullptr;
        m_workerMutex.unlock();
    }
}

void EzTuneGateway::StartWorker(ParameterEzTuneCom::SocketID& socket)
{
    WorkerInfo*  info   = new WorkerInfo(this, socket);
    std::thread* thread = new std::thread(&EzTuneGateway::GatewayWorkerThread, info);
    m_workerMutex.lock();
    m_workers.Add(WorkerThread(info, thread));
    m_workerMutex.unlock();
}

bool EzTuneGateway::IsPortActive(UINT16 port)
{
    bool result = false;
    m_workerMutex.lock();
    for (UINT32 i = 0; i < m_workers.Count(); i++)
    {
        if (m_workers[i].Info->Socket.SocketAddr.Port == port)
        {
            result = m_workers[i].Info->Active;

            if (!result)
            {
                PARAMETER_DELETE m_workers[i].Info;
                m_workers[i].Info = nullptr;
                m_workers.RemoveAt(i);
            }
            break;
        }
    }
    m_workerMutex.unlock();

    return result;
}

void EzTuneGateway::StartInternal()
{
    do
    {
        EzTuneGateway gateway;

        for (UINT8 i = 0; !ExitEvent.Wait(EzTuneEvent::CHECK_STATE) && i < m_maxPorts; i++)
        {
            UINT16 port = ParameterEzTuneComBase::InternalPort + i;
            ParameterEzTuneCom::SocketID socketID;
            if (!IsPortActive(port))
            {
                socketID.SocketAddr.SetupSocket(ParameterEzTuneCom::SocketAddress::AddrType::Internal, port);
                socketID.ConnectAddr.SetupSocket(ParameterEzTuneCom::SocketAddress::AddrType::Internal, port);
#ifdef _WIN32
                socketID.ID = socket(AF_INET, SOCK_STREAM, 0);
#else
                socketID.ID = socket(AF_UNIX, SOCK_STREAM, 0);
#endif
                INT32 error = ParameterEzTuneCom::GetError();

                if (socketID.ID != PARAMETER_INVALID_SOCKET)
                {
                    INT32 conn = connect(socketID.ID, (struct sockaddr*)&socketID.ConnectAddr.Address, socketID.ConnectAddr.Length);

                    error = ParameterEzTuneCom::GetError();
                    if (conn == 0)
                    {
                        socketID.Connection = socketID.ID;
                        PARAMETER_LOG_DEBUG(nullptr, "EzTune Gateway Server connect succeeded, Socket: %llu, Port: %s\n",
                            socketID.ID, socketID.SocketAddr.PortName);
                        StartWorker(socketID);
                    }
                    else
                    {
                        socketID.CloseSocket();
                    }
                }
            }
        }
    } while (!ExitEvent.Wait(DISCOVERY_PERIOD));
}

void EzTuneGateway::StartWorkerThread(void* obj)
{
    if (obj != nullptr)
    {
        ((EzTuneGateway*)obj)->StartInternal();
    }
}

void EzTuneGateway::GatewayWorkerThread(void* obj)
{
    WorkerInfo* info = (WorkerInfo*)obj;
    bool exit = false;

    // Setup the external gateway server socket for this internal socket
    UINT16 port = info->Socket.ConnectAddr.Port - ParameterEzTuneCom::InternalPort + ParameterEzTuneCom::ExternalPort;
    ParameterEzTuneCom::SocketID externalID;
    externalID.SocketAddr.SetupSocket(ParameterEzTuneCom::SocketAddress::AddrType::External, port);
    externalID.ConnectAddr.SetupSocket(ParameterEzTuneCom::SocketAddress::AddrType::External, port);

    // Open the external socket
    externalID.ID = socket(AF_INET, SOCK_STREAM, 0);
    INT32 error = ParameterEzTuneCom::GetError();

    if (externalID.ID != PARAMETER_INVALID_SOCKET)
    {
        // Open external socket succeeded, bind the socket
        INT32 bindResult = bind(externalID.ID, (struct sockaddr*)&externalID.SocketAddr.Address, externalID.SocketAddr.Length);

        if (bindResult >= 0)
        {
            constexpr INT32  BACKLOG = 5;

            // Bind external socket succeeded, start listening
            INT32 listenResult = listen(externalID.ID, BACKLOG);

            if (listenResult == 0)
            {
                // Listen succeeded
                const UINT32 MAX_SIZE = 65535;
                BYTE* readBuffer = PARAMETER_NEW BYTE[MAX_SIZE];

                if (readBuffer != nullptr)
                {
                    while (!exit && !info->Gateway->ExitEvent.Wait(EzTuneEvent::CHECK_STATE))
                    {
                        // Process Incomming
                        INT32 bytesRead = externalID.ReadData(readBuffer, ParameterEzTuneComBase::MAX_PACKET_LEN, PACKETS_PERIOD, exit);
                        if (!exit && bytesRead > 0)
                        {
                            INT32 writeResult = info->Socket.WriteData(readBuffer, bytesRead);
                            INT32 error = ParameterEzTuneCom::GetError();
                            if (writeResult != bytesRead)
                            {
#ifdef _WIN32
                                PARAMETER_LOG_DEBUG(nullptr, "EzTune Gateway Server -> EzTune write failed, Socket: %llu, Port: %s, Result: %d, Error: %d\n",
                                    externalID.ID, externalID.SocketAddr.PortName, writeResult, error);
#else
                                PARAMETER_LOG_DEBUG(nullptr, "EzTune Gateway Server -> EzTune write failed, Socket: %d, Port: %s: Result: %d, errno: %d\n",
                                    externalID.ID, externalID.SocketAddr.PortName, writeResult, error);
#endif
                            }
                        }

                        // Process Outgoing
                        bytesRead = info->Socket.ReadData(readBuffer, ParameterEzTuneComBase::MAX_PACKET_LEN, PACKETS_PERIOD, exit);
                        if (!exit && bytesRead > 0)
                        {
                            externalID.WriteData(readBuffer, bytesRead);
                        }
                    }
                }
                else
                {
#ifdef _WIN32
                    PARAMETER_LOG_DEBUG(nullptr, "EzTune Gateway Server failed to allocate read buffer, Socket: %llu, Port: %s\n",
                        externalID.ID, externalID.SocketAddr.PortName);
#else
                    PARAMETER_LOG_DEBUG(nullptr, "EzTune Gateway Server failed to allocate read buffer, Socket: %d, Port: %s\n",
                        externalID.ID, externalID.SocketAddr.PortName);
#endif
                }
            }
            else
            {
                // Listen failed
#ifdef _WIN32
                PARAMETER_LOG_DEBUG(nullptr, "EzTune Gateway Server external port listen failed, Socket: %llu, Port: %s, Result: %d\n",
                    externalID.ID, externalID.SocketAddr.PortName, listenResult);
#else
                PARAMETER_LOG_DEBUG(nullptr, "EzTune Gateway Server failed to allocate read buffer, Socket: %d, Port: %s: Result: %d\n",
                    externalID.ID, externalID.SocketAddr.PortName, listenResult);
#endif
            }
        }

        externalID.CloseSocket();
    }
    else
    {
#ifdef _WIN32
        PARAMETER_LOG_DEBUG(nullptr, "EzTune Gateway Server socket create failed, Socket: %llu, Port: %s: Error: %d\n",
            externalID.ID, externalID.SocketAddr.PortName, error);
#else
        PARAMETER_LOG_DEBUG(nullptr, "EzTune Gateway Server socket create failed, Socket: %d, Port: %s: errno: %d\n",
            externalID.ID, externalID.SocketAddr.PortName, error);
#endif
    }

    info->Socket.CloseSocket();
    info->Active = false;
}
