/*  -*- C++ -*-
 *  Copyright (C) 2003-2005 Thiago Macieira <thiago@kde.org>
 *
 *
 *  Permission is hereby granted, free of charge, to any person obtaining
 *  a copy of this software and associated documentation files (the
 *  "Software"), to deal in the Software without restriction, including
 *  without limitation the rights to use, copy, modify, merge, publish,
 *  distribute, sublicense, and/or sell copies of the Software, and to
 *  permit persons to whom the Software is furnished to do so, subject to
 *  the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included
 *  in all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 *  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 *  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 *  OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 *  WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#ifndef KRESOLVER_P_H
#define KRESOLVER_P_H

#include <config-network.h>
#include <sys/types.h>

#include <QByteArray>
#include <QList>
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include <QSemaphore>
#include <QEvent>

#include "k3resolver.h"

/* decide whether we need a mutex */
#if !HAVE_GETPROTOBYNAME_R || !HAVE_GETSERVBYNAME_R || !HAVE_GETHOSTBYNAME_R || !HAVE_GETSERVBYPORT_R
# define NEED_MUTEX
extern QMutex getXXbyYYmutex;
#endif

/* some systems have the functions, but don't declare them */
#if HAVE_GETSERVBYNAME_R && !HAVE_GETSERVBYNAME_R_PROTO
extern "C" {
    struct servent;
    extern int getservbyname_r(const char *serv, const char *proto,
                               struct servent *servbuf,
                               char *buf, size_t buflen,
                               struct servent **result);
    extern int getservbyport_r(int port, const char *proto,
                               struct servent *servbuf,
                               char *buf, size_t buflen,
                               struct servent **result);

    struct protoent;
    extern int getprotobyname_r(const char *proto, struct protoent *protobuf,
                                char *buf, size_t buflen,
                                struct protoent **result);
    extern int getprotobynumber_r(int proto, struct protoent *protobuf,
                                  char *buf, size_t buflen,
                                  struct protoent **result);
}
#endif

/* decide whether res_init is thread-safe or not */
#if defined(__GLIBC__)
# undef RES_INIT_THREADSAFE
#endif

namespace KNetwork
{
// defined in network/qresolverworkerbase.h
class KResolverWorkerBase;
class KResolverWorkerFactoryBase;
class KResolverPrivate;

namespace Internal
{
class KResolverManager;
class KResolverThread;
struct RequestData;

struct InputData {
    QString node, service;
    QByteArray protocolName;
    int flags;
    int familyMask;
    int socktype;
    int protocol;
};
}

class KResolverPrivate
{
public:
    // parent class. Should never be changed!
    KResolver *parent;
    bool deleteWhenDone : 1;
    bool waiting : 1;

    // class status. Should not be changed by worker threads!
    volatile int status;
    volatile int errorcode, syserror;

    // input data. Should not be changed by worker threads!
    Internal::InputData input;

    // mutex
    QMutex mutex;

    // output data
    KResolverResults results;

    explicit KResolverPrivate(KResolver *_parent,
                              const QString &_node = QString(),
                              const QString &_service = QString())
        : parent(_parent), deleteWhenDone(false), waiting(false),
          status(0), errorcode(0), syserror(0)
    {
        input.node = _node;
        input.service = _service;
        input.flags = 0;
        input.familyMask = KResolver::AnyFamily;
        input.socktype = 0;
        input.protocol = 0;

        results.setAddress(_node, _service);
    }
};

namespace Internal
{
struct RequestData {
    // worker threads should not change values in the input data
    KNetwork::KResolverPrivate *obj;
    const KNetwork::Internal::InputData *input;
    KNetwork::KResolverWorkerBase *worker; // worker class
    RequestData *requestor; // class that requested us

    volatile int nRequests; // how many requests that we made we still have left
};

/*
 * @internal
 * This class is the resolver manager
 */
class KResolverManager
{
public:
    enum EventTypes
    { ResolutionCompleted = 1576 }; // arbitrary value;

    /*
     * This wait condition is used to notify wait states (KResolver::wait) that
     * the resolver manager has finished processing one or more objects. All
     * objects in wait state will be woken up and will check if they are done.
     * If they aren't, they will go back to sleeping.
     */
    QWaitCondition notifyWaiters;

private:
    /*
     * This variable is used to count the number of threads that are running
     */
    volatile unsigned short runningThreads;

    /*
     * This variable is used to count the number of threads that are currently
     * waiting for data.
     */
    unsigned short availableThreads;

    /*
     * This wait condition is used to notify worker threads that there is new
     * data available that has to be processed. All worker threads wait on this
     * waitcond for a limited amount of time.
     */
    QWaitCondition feedWorkers;

    // this mutex protects the data in this object
    QMutex mutex;

    // hold a list of all the current threads we have
    QList<KResolverThread *> workers;

    // hold a list of all the new requests we have
    QList<RequestData *> newRequests;

    // hold a list of all the requests in progress we have
    QList<RequestData *> currentRequests;

    // hold a list of all the workers we have
    QList<KNetwork::KResolverWorkerFactoryBase *> workerFactories;

    // private constructor
    KResolverManager();

public:
    static KResolverManager *manager();  // creates and returns the global manager

    // destructor
    ~KResolverManager();

    /*
     * Register this thread in the pool
     */
    void registerThread(KResolverThread *id);

    /*
     * Unregister this thread from the pool
     */
    void unregisterThread(KResolverThread *id);

    /*
     * Requests new data to work on.
     *
     * This function should only be called from a worker thread. This function
     * is thread-safe.
     *
     * If there is data to be worked on, this function will return it. If there is
     * none, this function will return a null pointer.
     */
    RequestData *requestData(KResolverThread *id, int maxWaitTime);

    /*
     * Releases the resources and returns the resolved data.
     *
     * This function should only be called from a worker thread. It is
     * thread-safe. It does not post the event to the manager.
     */
    void releaseData(KResolverThread *id, RequestData *data);

    /*
     * Registers a new worker class by way of its factory.
     *
     * This function is NOT thread-safe.
     */
    void registerNewWorker(KNetwork::KResolverWorkerFactoryBase *factory);

    /*
     * Enqueues new resolutions.
     */
    void enqueue(KNetwork::KResolver *obj, RequestData *requestor);

    /*
     * Dispatch a new request
     */
    void dispatch(RequestData *data);

    /*
     * Dequeues a resolution.
     */
    void dequeue(KNetwork::KResolver *obj);

    /*
     * Notifies the manager that the given resolution is about to
     * be deleted. This function should only be called by the
     * KResolver destructor.
     */
    void aboutToBeDeleted(KNetwork::KResolver *obj);

    /*
     * Notifies the manager that new events are ready.
     */
    void newEvent();

    /*
     * This function is called by the manager to receive a new event. It operates
     * on the eventSemaphore() semaphore, which means it will block till there
     * is at least one event to go.
     */
    void receiveEvent();

private:
    /*
     * finds a suitable worker for this request
     */
    KNetwork::KResolverWorkerBase *findWorker(KNetwork::KResolverPrivate *p);

    /*
     * finds data for this request
     */
    RequestData *findData(KResolverThread *);

    /*
     * Handle completed requests.
     *
     * This function is called by releaseData above
     */
    void handleFinished();

    /*
     * Handle one completed request.
     *
     * This function is called by handleFinished above.
     */
    bool handleFinishedItem(RequestData *item);

    /*
     * Notifies the parent class that this request is done.
     *
     * This function deletes the request
     */
    void doNotifying(RequestData *p);

    /*
     * Dequeues and notifies an object that is in Queued state
     * Returns true if the object is no longer queued; false if it could not
     * be dequeued (i.e., it's running)
     */
    bool dequeueNew(KNetwork::KResolver *obj);
};

/*
 * @internal
 * This class is a worker thread in the resolver system.
 * This class must be thread-safe.
 */
class KResolverThread: public QThread
{
private:
    // private constructor. Only the manager can create worker threads
    KResolverThread();
    RequestData *data;

protected:
    void run() Q_DECL_OVERRIDE;       // here the thread starts

    friend class KNetwork::Internal::KResolverManager;
    friend class KNetwork::KResolverWorkerBase;

public:
    bool checkResolver(); // see KResolverWorkerBase::checkResolver
    void acquireResolver();   // see KResolverWorkerBase::acquireResolver
    void releaseResolver();   // see KResolverWorkerBase::releaseResolver
};

} // namespace Internal

} // namespace KNetwork

#endif
