/**
*
* @File       : Complément de nsNet.cxx
*
* @Version    : V1.0
* @Authors    : D. Mathieu, M. Laporte, C. Pain-Barre
*
* @Last modified : 21/12/2001
*
**/
#include <algorithm>      // max()
#include <cerrno>         // EINPROGRESS

#include <fcntl.h>        // F_SETFL, F_GETFL, O_NONBLOCK
#include <netinet/in.h>   // sockaddr_in, in_addr
#include <sys/select.h>   // FD_ZERO, FD_ISSET
#include <sys/socket.h>   // connect(), SOL_SOCKET, SOL_ERROR

#include "nsSysteme.h"    // Fcntl(), Select(), Close()
#include "nsNet.h"        //
#include "CException.h"
#include "CExcFctSyst.h"
#include "CstCodErr.h"
#include "CstCodErrNet.h"
#include "TimeBase.h"

using namespace nsUtil;
using namespace nsSysteme;

namespace nsNet
{
    void ClientConnectSockIn (int sd, const ::in_addr & Adresse,
                                      unsigned short int Port,
                                      unsigned TimeOut = 0)
        throw (nsUtil::CException)
    {
        CTimevalBase Time (TimeOut);

        int OldFlags = Fcntl (sd, F_GETFL);
        Fcntl (sd, F_SETFL, OldFlags | O_NONBLOCK);

        ::sockaddr_in Addr;
        Init_AddrIn (Addr, Port, Adresse);

        if (::connect (sd, reinterpret_cast < ::sockaddr *> (& Addr),
                           sizeof (Addr)))
        {
            if (EINPROGRESS != errno)
                throw CExcFctSystFile ("connect()", sd);

            ::fd_set MaskWrite;
            FD_ZERO (&MaskWrite);
            FD_SET  (sd, & MaskWrite);

            if (!Select (sd + 1, 0, &MaskWrite, 0, & Time))
            {
                Close (sd);
                throw CException ("TimeOut de connexion", CstTimeOut);
            }

            int Error;
            ::socklen_t len = sizeof (Error);
            if (::getsockopt (sd, SOL_SOCKET, SO_ERROR, &Error, &len))
                throw CExcFctSystFile ("getsockopt()", sd);

            if ((errno = Error))
            {
                Close (sd);
                throw CExcFctSystFile ("connect()", sd);
            }
        }
        Fcntl (sd, F_SETFL, OldFlags);

    } // ClientConnectSockIn()

    void ClientMultiConnectSockIn (int NbConnect,
                                   int                sd      [],
                                   ::in_addr          Adresse [],
                                   unsigned short int Port    [],
                                   int                CodErr  [],
                                   unsigned TimeOut = 0)
        throw (nsUtil::CException)   
    {  
        int  OldFlags    [NbConnect];

        CTimevalBase Time (TimeOut);

        ::fd_set MaskSuspended;
        FD_ZERO (&MaskSuspended);
        int NbInProgress = 0;
        int MaxSd = 0;
        for (int i = NbConnect; i--; )
        {
            OldFlags [i] = Fcntl (sd [i], F_GETFL);
            Fcntl (sd [i], F_SETFL, OldFlags [i] | O_NONBLOCK);

            ::sockaddr_in Addr;
            Init_AddrIn (Addr, Port [i], Adresse [i]);

            if (::connect (sd [i],
                           reinterpret_cast < ::sockaddr *> (& Addr),
                           sizeof (Addr)))
                if (EINPROGRESS != errno)
                    CodErr [i] = errno;
                else
                {
                    FD_SET (sd [i], & MaskSuspended);
                    MaxSd = max (MaxSd, sd [i]);
                    ++NbInProgress;
                }
            else
                CodErr [i] = CstNoError;
        }
        int NbEvent;
        int Error;
        socklen_t len = sizeof (Error);

        // Tant qu'il y a des connexions en suspens et que des
        //   événements arrivent (pas de timeout)

        for (; NbInProgress &&
               (NbEvent = Select (MaxSd + 1, 0,
                &MaskWrite, 0, & Time)); )
        {
            ::fd_set MaskWrite = MaskSuspended;
            for (int i = NbConnect; i--; )
                if (FD_ISSET (sd [i], &MaskWrite))
                {
                    FD_CLR (sd [i], &MaskSuspended);

                    if (::getsockopt (sd [i], SOL_SOCKET, SO_ERROR,
                                            &Error, &len))
                        CodErr [i] = errno;
                    else
                        CodErr [i] = (Error) ? Error : CstNoError;
                }
                        
            NbInProgress -= NbEvent;
        }
        for (int i = NbConnect; i--; )
        {
            // Tous les sd qui restent positionnés dans le masque
            //   correspondent à des TimeOut

            if (FD_ISSET (sd [i], &MaskSuspended)
                CodErr [i] = CstTimeOut;
            Fcntl (sd [i], F_SETFL, OldFlags [i]);
        }

    } // ClientMultiConnectSockIn()

} // namespace nsNet