/*
 * Copyright (C) 2007-2023 Xagasoft, All rights reserved.
 *
 * This file is part of the libbu++ library and is released under the
 * terms of the license contained in the file LICENSE.
 */

#ifndef BU_SERVER_H
#define BU_SERVER_H

#include <stdint.h>

#include "bu/string.h"
#include "bu/list.h"

#include "bu/clientlink.h"
#include "bu/clientlinkfactory.h"
#include "bu/hash.h"
#include "bu/synchroqueue.h"
#include "bu/thread.h"
#include "bu/counterevent.h"

#include "bu/config.h"

#ifndef PROFILE_BU_SERVER
// #define PROFILE_BU_SERVER   1
#endif

#ifdef PROFILE_BU_SERVER
#include "bu/profiler.h"
#endif

namespace Bu
{
    class ServerSocket;
    class Socket;
    class Client;

    /**
     * Core of a network server.  This class is distinct from a ServerSocket in
     * that a ServerSocket is one listening socket, nothing more.  Socket will
     * manage a pool of both ServerSockets and connected Sockets along with
     * their protocols and buffers.
     *
     * To start serving on a new port, use the addPort functions.  Each call to
     * addPort creates a new ServerSocket, starts it listening, and adds it to
     * the server pool.
     *
     * All of the real work is done by scan, which will wait for up
     * to the timeout set by setTimeout before returning if there is no data
     * pending.  scan should probably be called in some sort of tight
     * loop, possibly in it's own thread, or in the main control loop.
     *
     * In order to use a Server you must subclass it and implement the pure
     * virtual functions.  These allow you to receive notification of events
     * happening within the server itself, and actually makes it useful.
     *@ingroup Serving
     */
    class Server
    {
    friend class ServerInterface;
    public:
        Server( int iIoWorkers=4, int iClientWorkers=8 );
        virtual ~Server();

#ifdef WIN32
        typedef unsigned int fd;
#else
        typedef int fd;
#endif

        void addServerSocket( Bu::ServerSocket *pSocket );

        virtual void scan();
        void setTimeout( int nTimeoutSec, int nTimeoutUSec=0 );

        void addClient( const Bu::ServerSocket *pSrv, Bu::Socket *pSocket );
        Bu::Client *getClient( fd iId );
        bool getClientAndSocket( fd iId, Bu::Client *&pClient,
                Bu::Socket *&pSocket );

        void setAutoTick( bool bEnable=true );
        void tick();

        virtual void onNewConnection( const Bu::ServerSocket *pSrv, Client *pClient, Bu::Socket *pSocket )=0;
        virtual void onClosedConnection( Client *pClient )=0;

        void shutdown();

    protected:
        void clientReadReady( fd iFd );
        void clientWriteReady( fd iFd );
        void closeClient( fd iSocket );

    private:
        class SrvClientLink : public Bu::ClientLink
        {
        public:
            SrvClientLink( Bu::Client *pClient );
            virtual ~SrvClientLink();

            virtual void sendMessage( const Bu::String &sMsg );

        private:
            Bu::Client *pClient;
        };

        class SrvClientLinkFactory : public Bu::ClientLinkFactory
        {
        public:
            SrvClientLinkFactory();
            virtual ~SrvClientLinkFactory();

            virtual Bu::ClientLink *createLink( Bu::Client *pClient );
        };

        class WriteMonitor : public Bu::Thread
        {
        public:
            WriteMonitor( Server &rSrv );
            virtual ~WriteMonitor();

        protected:
            virtual void run();

        private:
            Server &rSrv;
        };

        class Event
        {
        public:
            enum Operation
            {
                Read,
                Write,
                Process
            };
            Event( fd iId, Operation eOp );
            ~Event();

            fd getId() const;
            Operation getOperation() const;

        private:
            fd iId;
            Operation eOp;
        };

        typedef Bu::SynchroQueue<Event *> EventQueue;

        class IoWorker : public Bu::Thread
        {
        public:
            IoWorker( Server &rSrv );
            virtual ~IoWorker();

        protected:
            virtual void run();

        private:
            void handleRead( Client *pClient, Socket *pSocket );
            void handleWrite( Client *pClient, Socket *pSocket );

        private:
            Server &rSrv;
        };
        friend class Bu::Server::IoWorker;

        class ClientWorker : public Bu::Thread
        {
        public:
            ClientWorker( Server &rSrv );
            virtual ~ClientWorker();

        protected:
            virtual void run();

        private:
            Server &rSrv;
        };
        friend class Bu::Server::ClientWorker;

        class __ServerCore *pCore;
        int nTimeoutSec;
        int nTimeoutUSec;
        typedef Hash<fd,ServerSocket *> SrvHash;
        SrvHash hServers;
        typedef Hash<fd,Client *> ClientHash;
        typedef Hash<fd,Socket *> SocketHash;
        ClientHash hClients;
        SocketHash hSockets;
        bool bAutoTick;
        Bu::Mutex mClients;
        Bu::Mutex mScan;
        Bu::Mutex mWorkers;

        EventQueue qIoEvent;
        EventQueue qClientEvent;
        typedef List<IoWorker *> IoWorkerList;
        typedef List<ClientWorker *> ClientWorkerList;
        IoWorkerList lIoWorker;
        ClientWorkerList lClientWorker;
        WriteMonitor tMonitorWrite;
        bool bRunning;
    };
}

#endif
