/*
  Copyright (C) 2010-2011  Charles Pence <charles@charlespence.net>
  Copyright (C) 1996-1997  Id Software, Inc.

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "pch.h"
#include "quakedef.h"

qsocket_t    *net_activeSockets = NULL;
qsocket_t    *net_freeSockets = NULL;
int            net_numsockets = 0;

qboolean    serialAvailable = false;
qboolean    ipxAvailable = false;
qboolean    tcpipAvailable = false;

int            net_hostport;
int            DEFAULTnet_hostport = 26000;

char        my_ipx_address[NET_NAMELEN];
char        my_tcpip_address[NET_NAMELEN];

void (*GetComPortConfig) (int portNumber, int *port, int *irq, int *baud, qboolean *useModem);
void (*SetComPortConfig) (int portNumber, int port, int irq, int baud, qboolean useModem);
void (*GetModemConfig) (int portNumber, char *dialType, char *clear, char *init, char *hangup);
void (*SetModemConfig) (int portNumber, char *dialType, char *clear, char *init, char *hangup);

static qboolean    listening = false;


sizebuf_t net_message;
int net_activeconnections = 0;

int messagesSent = 0;
int messagesReceived = 0;
int unreliableMessagesSent = 0;
int unreliableMessagesReceived = 0;

// these two macros are to make the code more readable
#define sfunc    net_drivers[sock->driver]
#define dfunc    net_drivers[net_driverlevel]

int    net_driverlevel;


double            net_time;

double SetNetTime(void)
{
    net_time = Sys_FloatTime();
    return net_time;
}


/*
===================
NET_NewQSocket

Called by drivers when a new communications endpoint is required
The sequence and buffer fields will be filled in properly
===================
*/
qsocket_t *NET_NewQSocket (void)
{
    qsocket_t    *sock;

    if (net_freeSockets == NULL)
        return NULL;

    if (net_activeconnections >= svs.maxclients)
        return NULL;

    // get one from free list
    sock = net_freeSockets;
    net_freeSockets = sock->next;

    // add it to active list
    sock->next = net_activeSockets;
    net_activeSockets = sock;

    sock->disconnected = false;
    sock->connecttime = net_time;
    strcpy (sock->address,"UNSET ADDRESS");
    sock->driver = net_driverlevel;
    sock->socket = 0;
    sock->driverdata = NULL;
    sock->canSend = true;
    sock->sendNext = false;
    sock->lastMessageTime = net_time;
    sock->ackSequence = 0;
    sock->sendSequence = 0;
    sock->unreliableSendSequence = 0;
    sock->sendMessageLength = 0;
    sock->receiveSequence = 0;
    sock->unreliableReceiveSequence = 0;
    sock->receiveMessageLength = 0;

    return sock;
}


void NET_FreeQSocket(qsocket_t *sock)
{
    qsocket_t    *s;

    // remove it from active list
    if (sock == net_activeSockets)
        net_activeSockets = net_activeSockets->next;
    else
    {
        for (s = net_activeSockets; s; s = s->next)
            if (s->next == sock)
            {
                s->next = sock->next;
                break;
            }
        if (!s)
            Sys_Error ("NET_FreeQSocket: not active\n");
    }

    // add it to free list
    sock->next = net_freeSockets;
    net_freeSockets = sock;
    sock->disconnected = true;
}


static void NET_Listen_f (void)
{
    if (Cmd_Argc () != 2)
    {
        Con_Printf ("\"listen\" is \"%u\"\n", listening ? 1 : 0);
        return;
    }

    listening = Util::lexical_cast<int>(Cmd_Argv(1)) ? true : false;

    for (net_driverlevel=0 ; net_driverlevel<net_numdrivers; net_driverlevel++)
    {
        if (net_drivers[net_driverlevel].initialized == false)
            continue;
        dfunc.Listen (listening);
    }
}


static void MaxPlayers_f (void)
{
    int     n;

    if (Cmd_Argc () != 2)
    {
        Con_Printf ("\"maxplayers\" is \"%u\"\n", svs.maxclients);
        return;
    }

    if (sv.active)
    {
        Con_Printf ("maxplayers can not be changed while a server is running.\n");
        return;
    }

    n = Util::lexical_cast<int>(Cmd_Argv(1));
    if (n < 1)
        n = 1;
    if (n > svs.maxclientslimit)
    {
        n = svs.maxclientslimit;
        Con_Printf ("\"maxplayers\" set to \"%u\"\n", n);
    }

    if ((n == 1) && listening)
        Cbuf_AddText ("listen 0\n");

    if ((n > 1) && (!listening))
        Cbuf_AddText ("listen 1\n");

    svs.maxclients = n;
    if (n == 1)
        Cvar_Set ("deathmatch", "0");
    else
        Cvar_Set ("deathmatch", "1");
}


static void NET_Port_f (void)
{
    int     n;

    if (Cmd_Argc () != 2)
    {
        Con_Printf ("\"port\" is \"%u\"\n", net_hostport);
        return;
    }

    n = Util::lexical_cast<int>(Cmd_Argv(1));
    if (n < 1 || n > 65534)
    {
        Con_Printf ("Bad value, must be between 1 and 65534\n");
        return;
    }

    DEFAULTnet_hostport = n;
    net_hostport = n;

    if (listening)
    {
        // force a change to the new port
        Cbuf_AddText ("listen 0\n");
        Cbuf_AddText ("listen 1\n");
    }
}


/*
===================
NET_Connect
===================
*/
qsocket_t *NET_Connect(const std::string &host)
{
    qsocket_t *ret;
    int numdrivers = net_numdrivers;

    SetNetTime();

    if (boost::iequals(host, "local"))
        numdrivers = 1;
    else
        return NULL;

    for (net_driverlevel=0 ; net_driverlevel<numdrivers; net_driverlevel++)
    {
        if (net_drivers[net_driverlevel].initialized == false)
            continue;
        ret = dfunc.Connect (host);
        if (ret)
            return ret;
    }

    return NULL;
}


/*
===================
NET_CheckNewConnections
===================
*/
qsocket_t *NET_CheckNewConnections (void)
{
    qsocket_t    *ret;

    SetNetTime();

    for (net_driverlevel=0 ; net_driverlevel<net_numdrivers; net_driverlevel++)
    {
        if (net_drivers[net_driverlevel].initialized == false)
            continue;
        if (net_driverlevel && listening == false)
            continue;
        ret = dfunc.CheckNewConnections ();
        if (ret)
            return ret;
    }

    return NULL;
}

/*
===================
NET_Close
===================
*/
void NET_Close (qsocket_t *sock)
{
    if (!sock)
        return;

    if (sock->disconnected)
        return;

    SetNetTime();

    // call the driver_Close function
    sfunc.Close (sock);

    NET_FreeQSocket(sock);
}


/*
=================
NET_GetMessage

If there is a complete message, return it in net_message

returns 0 if no data is waiting
returns 1 if a message was received
returns -1 if connection is invalid
=================
*/
extern void PrintStats(qsocket_t *s);

int    NET_GetMessage (qsocket_t *sock)
{
    int ret;

    if (!sock)
        return -1;

    if (sock->disconnected)
    {
        Con_Printf("NET_GetMessage: disconnected socket\n");
        return -1;
    }

    SetNetTime();

    ret = sfunc.QGetMessage(sock);

    // see if this connection has timed out
    if (ret == 0 && sock->driver)
    {
        if (net_time - sock->lastMessageTime > Cvar_VariableValue("net_messagetimeout"))
        {
            NET_Close(sock);
            return -1;
        }
    }


    if (ret > 0)
    {
        if (sock->driver)
        {
            sock->lastMessageTime = net_time;
            if (ret == 1)
                messagesReceived++;
            else if (ret == 2)
                unreliableMessagesReceived++;
        }
    }

    return ret;
}


/*
==================
NET_SendMessage

Try to send a complete length+message unit over the reliable stream.
returns 0 if the message cannot be delivered reliably, but the connection
        is still considered valid
returns 1 if the message was sent properly
returns -1 if the connection died
==================
*/
int NET_SendMessage (qsocket_t *sock, sizebuf_t *data)
{
    int        r;

    if (!sock)
        return -1;

    if (sock->disconnected)
    {
        Con_Printf("NET_SendMessage: disconnected socket\n");
        return -1;
    }

    SetNetTime();
    r = sfunc.QSendMessage(sock, data);
    if (r == 1 && sock->driver)
        messagesSent++;

    return r;
}


int NET_SendUnreliableMessage (qsocket_t *sock, sizebuf_t *data)
{
    int        r;

    if (!sock)
        return -1;

    if (sock->disconnected)
    {
        Con_Printf("NET_SendMessage: disconnected socket\n");
        return -1;
    }

    SetNetTime();
    r = sfunc.SendUnreliableMessage(sock, data);
    if (r == 1 && sock->driver)
        unreliableMessagesSent++;

    return r;
}


/*
==================
NET_CanSendMessage

Returns true or false if the given qsocket can currently accept a
message to be transmitted.
==================
*/
qboolean NET_CanSendMessage (qsocket_t *sock)
{
    int        r;

    if (!sock)
        return false;

    if (sock->disconnected)
        return false;

    SetNetTime();

    r = sfunc.CanSendMessage(sock);
    return r;
}


int NET_SendToAll(sizebuf_t *data, int blocktime)
{
    double        start;
    int            i;
    int            count = 0;
    qboolean    state1 [MAX_SCOREBOARD];
    qboolean    state2 [MAX_SCOREBOARD];

    for (i=0, host_client = svs.clients ; i<svs.maxclients ; i++, host_client++)
    {
        if (!host_client->netconnection)
            continue;
        if (host_client->active)
        {
            if (host_client->netconnection->driver == 0)
            {
                NET_SendMessage(host_client->netconnection, data);
                state1[i] = true;
                state2[i] = true;
                continue;
            }
            count++;
            state1[i] = false;
            state2[i] = false;
        }
        else
        {
            state1[i] = true;
            state2[i] = true;
        }
    }

    start = Sys_FloatTime();
    while (count)
    {
        count = 0;
        for (i=0, host_client = svs.clients ; i<svs.maxclients ; i++, host_client++)
        {
            if (! state1[i])
            {
                if (NET_CanSendMessage (host_client->netconnection))
                {
                    state1[i] = true;
                    NET_SendMessage(host_client->netconnection, data);
                }
                else
                {
                    NET_GetMessage (host_client->netconnection);
                }
                count++;
                continue;
            }

            if (! state2[i])
            {
                if (NET_CanSendMessage (host_client->netconnection))
                {
                    state2[i] = true;
                }
                else
                {
                    NET_GetMessage (host_client->netconnection);
                }
                count++;
                continue;
            }
        }
        if ((Sys_FloatTime() - start) > blocktime)
            break;
    }
    return count;
}


//=============================================================================

/*
====================
NET_Init
====================
*/

void NET_Init (void)
{
    int            i;
    int            controlSocket;
    qsocket_t    *s;

    net_hostport = DEFAULTnet_hostport;

// FIXME maxclients multiplayer
//    listening = true;
    net_numsockets = svs.maxclientslimit + 1;

    SetNetTime();

    for (i = 0; i < net_numsockets; i++)
    {
        s = (qsocket_t *)Hunk_AllocName(sizeof(qsocket_t), "qsocket");
        s->next = net_freeSockets;
        net_freeSockets = s;
        s->disconnected = true;
    }

    // allocate space for network message buffer
    SZ_Alloc (&net_message, NET_MAXMESSAGE);

    Cvar_RegisterVariable ("net_messagetimeout", "300");
    Cvar_RegisterVariable ("hostname", "UNNAMED");

    Cmd_AddCommand ("listen", NET_Listen_f);
    Cmd_AddCommand ("maxplayers", MaxPlayers_f);
    Cmd_AddCommand ("port", NET_Port_f);

    // initialize all the drivers
    for (net_driverlevel=0 ; net_driverlevel<net_numdrivers ; net_driverlevel++)
    {
        controlSocket = net_drivers[net_driverlevel].Init();
        if (controlSocket == -1)
            continue;
        net_drivers[net_driverlevel].initialized = true;
        net_drivers[net_driverlevel].controlSock = controlSocket;
        if (listening)
            net_drivers[net_driverlevel].Listen (true);
    }

    if (*my_ipx_address)
        Con_DPrintf("IPX address %s\n", my_ipx_address);
    if (*my_tcpip_address)
        Con_DPrintf("TCP/IP address %s\n", my_tcpip_address);
}

/*
====================
NET_Shutdown
====================
*/

void        NET_Shutdown (void)
{
    qsocket_t    *sock;

    SetNetTime();

    for (sock = net_activeSockets; sock; sock = sock->next)
        NET_Close(sock);

//
// shutdown the drivers
//
    for (net_driverlevel = 0; net_driverlevel < net_numdrivers; net_driverlevel++)
    {
        if (net_drivers[net_driverlevel].initialized == true)
        {
            net_drivers[net_driverlevel].Shutdown ();
            net_drivers[net_driverlevel].initialized = false;
        }
    }
}

