#include <assert.h>

#include "ace/Reactor.h"
#include "ace/TP_Reactor.h"
#include "ace/Thread_Manager.h"
#include "curl/multi.h"

#include "vn_resource_pool.hpp"

#include <vector>


static int done = 0;

template <>
class ACE_Export VN_Resource_Create<CURL>
{
  public:
   CURL *operator () () const throw (resource_error)
   { 
       CURL *curl = curl_easy_init();
       return curl;

   };
};

template <>
class ACE_Export VN_Resource_Destroy<CURL>
{
  public:
   void operator () (CURL *curl) const 
   { 
       curl_easy_cleanup(curl);
   };
};

VN_Resource_Pool<CURL> pool(30);

/*
template <>
class ACE_Export VN_Resource_Check<CURL>
{
  public:
   bool operator () (CURL *curl) const 
   { 
      return curl != 0;
   };
};


class VN_CURL_Pool : public VN_Resource_Pool<CURL>
{
public:
      VN_CURL_Pool() : VN_Resource_Pool<CURL>(MAX_CONNECTIONS) {}

private:
     enum { MAX_CONNECTIONS = 16 };
};

//typedef ACE_Singleton<EL_PG_Conn_Pool, ACE_Thread_Mutex> EL_PG_CONN_POOL;

VN_CURL_Pool pool;
*/

struct CURL_Private
{
    CURL_Private() : res(0), user_data(0) {}

    VN_Resource<CURL> *res;

    void *user_data;
    // .. ?
};

class CURL_Event_Handler : public ACE_Event_Handler
{
public:

public:
    CURL_Event_Handler()
    {
        cm_ = curl_multi_init();

        FD_ZERO(&rfds_);
        FD_ZERO(&wfds_);
        FD_ZERO(&efds_);
    }

    virtual ~CURL_Event_Handler()
    {
        ACE_Guard<ACE_Recursive_Thread_Mutex> g(lock_);

        ACE_Reactor::instance()->remove_handler(this, READ_MASK);
        ACE_Reactor::instance()->remove_handler(this, WRITE_MASK);
        ACE_Reactor::instance()->remove_handler(this, EXCEPT_MASK);
        ACE_Reactor::instance()->remove_handler(this, TIMER_MASK);

        curl_multi_cleanup(cm_);
        
        cm_ = 0;
    }

public:

    void perform(const VN_Resource<CURL> &curl)
    {
        // make own copy of resource to increase counter
        // we will delete it later when URL will be processed
        VN_Resource<CURL> *curl_copy = new VN_Resource<CURL>(curl);

        // also set VN_Resource<CURL*> poiter to private struct
        CURL_Private *p;
        curl_easy_getinfo(curl, CURLINFO_PRIVATE, (char**)&p);

        if (!p)
        {
            p = new CURL_Private;
            curl_easy_setopt(curl, CURLOPT_PRIVATE, p);
        }

        p->res = curl_copy;
        
        ACE_Reactor::instance()->schedule_timer(this, curl_copy, ACE_Time_Value(0));
    }

    virtual int handle_timeout(const ACE_Time_Value &current_time, const void *act)
    {
        ACE_Guard<ACE_Recursive_Thread_Mutex> g(lock_);

        if (act != 0)
        {
            VN_Resource<CURL> *curl = (VN_Resource<CURL> *)act;
            curl_multi_add_handle(cm_, (CURL*)*curl);
        }

        ACE_Reactor::instance()->remove_handler(ACE_Handle_Set(rfds_), READ_MASK);
        ACE_Reactor::instance()->remove_handler(ACE_Handle_Set(wfds_), WRITE_MASK);
        ACE_Reactor::instance()->remove_handler(ACE_Handle_Set(efds_), EXCEPT_MASK);

        int running_handles;
        curl_multi_perform(cm_, &running_handles);

        if (running_handles == 0)
        {
            fprintf(stderr, "running_handlers = 0\n");

            handle_curl_multi(ACE_INVALID_HANDLE);

            return 0;
        }

        FD_ZERO(&rfds_);
        FD_ZERO(&wfds_);
        FD_ZERO(&efds_);

        int max_fd;
        if (curl_multi_fdset(cm_, &rfds_, &wfds_, &efds_, &max_fd)) 
        {
            fprintf(stderr, "E: curl_multi_fdset\n");
            return 0;
        }

        if (max_fd == -1) // fdset not ready (http://curl.haxx.se/libcurl/c/curl_multi_fdset.html)
        {
            fprintf(stderr, "max_fd == -1\n");
            ACE_Reactor::instance()->schedule_timer(this, 0, ACE_Time_Value(0, 100000));
            return 0;
        }
       
        
        ACE_Reactor::instance()->register_handler(ACE_Handle_Set(rfds_), this, READ_MASK);
        ACE_Reactor::instance()->register_handler(ACE_Handle_Set(wfds_), this, WRITE_MASK);
        ACE_Reactor::instance()->register_handler(ACE_Handle_Set(efds_), this, EXCEPT_MASK);

        return 0;
    }

    virtual int handle_output(ACE_HANDLE fd)
    {
        ACE_Guard<ACE_Recursive_Thread_Mutex> g(lock_);

        handle_curl_multi(fd);

        return -1;
    }
 
    virtual int handle_exception(ACE_HANDLE fd)
    {
        ACE_Guard<ACE_Recursive_Thread_Mutex> g(lock_);

        handle_curl_multi(fd);

        return -1;
    }

    virtual int handle_input(ACE_HANDLE fd)
    {
        ACE_Guard<ACE_Recursive_Thread_Mutex> g(lock_);

        handle_curl_multi(fd);

        return -1;

    }

private:

    int handle_curl_multi(ACE_HANDLE fd)
    {
        CURLMsg *msg;
        int msg_in_queue;
        while ((msg = curl_multi_info_read(cm_, &msg_in_queue))) 
        {
            if (msg->msg == CURLMSG_DONE) 
            {
                CURL_Private *p;
                CURL *e = msg->easy_handle;
                curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &p);
                assert(p);
                fprintf(stderr, "R: (%d) %d - %s <%s>\n",
                        ++done, msg->data.result, curl_easy_strerror(msg->data.result), (char*)p->user_data);
                curl_multi_remove_handle(cm_, e);

                delete p->res;
                delete p;
//                curl_easy_cleanup(e);
            }
            else {
                fprintf(stderr, "E: CURLMsg (%d)\n", msg->msg);
            }
        }


        if (fd != ACE_INVALID_HANDLE)
            ACE_Reactor::instance()->schedule_timer(this, (void*)0, ACE_Time_Value(0));

        return 0;
    }


private:
    CURLM *cm_;
    ACE_Recursive_Thread_Mutex lock_;
    fd_set rfds_, wfds_, efds_;  
};



static const char *urls[] = {
    "http://192.168.0.197/sdi/login/index.php",
    "http://192.168.0.199/sdi/login/index.php",
    "http://192.168.0.110/sdi/login/index.php",
    "http://192.168.0.197/sdi/login/index.php",
    "http://192.168.0.199/sdi/login/index.php",
    "http://192.168.0.110/sdi/login/index.php",
    "http://192.168.0.197/sdi/login/index.php",
    "http://192.168.0.199/sdi/login/index.php",
    "http://192.168.0.110/sdi/login/index.php",
    "http://192.168.0.197/sdi/login/index.php",
    "http://192.168.0.199/sdi/login/index.php",
    "http://192.168.0.110/sdi/login/index.php",
    "http://192.168.0.197/sdi/login/index.php",
    "http://192.168.0.199/sdi/login/index.php",
    "http://192.168.0.110/sdi/login/index.php",
    "http://192.168.0.197/sdi/login/index.php",
    "http://192.168.0.199/sdi/login/index.php",
    "http://192.168.0.110/sdi/login/index.php",
    "http://192.168.0.197/sdi/login/index.php",
    "http://192.168.0.199/sdi/login/index.php",
    "http://192.168.0.110/sdi/login/index.php",
    "http://192.168.0.197/sdi/login/index.php",
    "http://192.168.0.199/sdi/login/index.php",
    "http://192.168.0.110/sdi/login/index.php",
    "http://192.168.0.197/sdi/login/index.php",
    "http://192.168.0.199/sdi/login/index.php",
    "http://192.168.0.110/sdi/login/index.php",
    "http://192.168.0.197/sdi/login/index.php",
    "http://192.168.0.199/sdi/login/index.php",
    "http://192.168.0.110/sdi/login/index.php",
    "http://192.168.0.197/sdi/login/index.php",
    "http://192.168.0.199/sdi/login/index.php",
    "http://192.168.0.110/sdi/login/index.php",
    "http://192.168.0.197/sdi/login/index.php",
    "http://192.168.0.199/sdi/login/index.php",
    "http://192.168.0.110/sdi/login/index.php",
    "http://192.168.0.197/sdi/login/index.php",
    "http://192.168.0.199/sdi/login/index.php",
    "http://192.168.0.110/sdi/login/index.php",
    "http://192.168.0.197/sdi/login/index.php",
    "http://192.168.0.199/sdi/login/index.php",
    "http://192.168.0.110/sdi/login/index.php",
    "http://192.168.0.197/sdi/login/index.php",
    "http://192.168.0.199/sdi/login/index.php",
    "http://192.168.0.110/sdi/login/index.php",
    "http://192.168.0.197/sdi/login/index.php",
    "http://192.168.0.199/sdi/login/index.php",
    "http://192.168.0.110/sdi/login/index.php",
    "http://192.168.0.197/sdi/login/index.php",
    "http://192.168.0.199/sdi/login/index.php",
    "http://192.168.0.110/sdi/login/index.php",
    "http://192.168.0.197/sdi/login/index.php",
    "http://192.168.0.199/sdi/login/index.php",
    "http://192.168.0.110/sdi/login/index.php",
  "http://www.microsoft.com",
  "http://www.opensource.org",
  "http://www.google.com",
  "http://www.yahoo.com",
  "http://www.ibm.com",
  "http://www.mysql.com",
  "http://www.oracle.com",
  "http://www.ripe.net",
  "http://www.iana.org",
  "http://www.amazon.com",
  "http://www.netcraft.com",
  "http://www.heise.de",
  "http://www.chip.de",
  "http://www.ca.com",
  "http://www.cnet.com",
  "http://www.news.com",
  "http://www.cnn.com",
  "http://www.wikipedia.org",
  "http://www.dell.com",
  "http://www.hp.com",
  "http://www.cert.org",
  "http://www.mit.edu",
  "http://www.nist.gov",
  "http://www.ebay.com",
  "http://www.playstation.com",
  "http://www.uefa.com",
  "http://www.ieee.org",
  "http://www.apple.com",
  "http://www.sony.com",
  "http://www.symantec.com",
  "http://www.zdnet.com",
  "http://www.fujitsu.com",
  "http://www.supermicro.com",
  "http://www.hotmail.com",
  "http://www.ecma.com",
  "http://www.bbc.co.uk",
  "http://news.google.com",
  "http://www.foxnews.com",
  "http://www.msn.com",
  "http://www.wired.com",
  "http://www.sky.com",
  "http://www.usatoday.com",
  "http://www.cbs.com",
  "http://www.nbc.com",
  "http://slashdot.org",
  "http://www.bloglines.com",
  "http://www.techweb.com",
  "http://www.newslink.org",
  "http://www.un.org",
};

static ACE_THR_FUNC_RETURN event_loop(void *arg) 
{
    ACE_Reactor *reactor = static_cast<ACE_Reactor *>(arg);
    reactor->owner(ACE_OS::thr_self());	
    reactor->run_reactor_event_loop();
    return 0;
}


#define CNT sizeof(urls)/sizeof(char*) /* total number of transfers to do */

static size_t cb(char *d, size_t n, size_t l, void *p)
{
  /* take care of the data here, ignored in this example */
  (void)d;
  (void)p;
  return n*l;
}

int main(int argc, char *argv[])
{
    ACE_TP_Reactor *tp_reactor(new ACE_TP_Reactor);
    delete ACE_Reactor::instance(new ACE_Reactor(tp_reactor));

    ACE_Thread_Manager::instance()->spawn_n
        (1, event_loop, ACE_Reactor::instance());
    
    curl_global_init(CURL_GLOBAL_ALL);


    CURL_Event_Handler eh;


    printf("Num of URLS: %d\n", CNT);


    while (1)
    {
        std::vector<CURL*> curls;
        for (size_t i = 0; i < sizeof(urls)/sizeof(char*); ++i) 
        {
            VN_Resource<CURL> curl = pool.get();

            curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, cb);
            curl_easy_setopt(curl, CURLOPT_HEADER, 0L);
            curl_easy_setopt(curl, CURLOPT_URL, urls[i]);

            CURL_Private *p = new CURL_Private;
            p->user_data = (void*)urls[i];
            curl_easy_setopt(curl, CURLOPT_PRIVATE, p);
            curl_easy_setopt(curl, CURLOPT_VERBOSE, 0L);
            curl_easy_setopt(curl, CURLOPT_TIMEOUT, 15);
            curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
            curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0");
            curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); 
            curl_easy_setopt(curl, CURLOPT_COOKIEFILE, ""); /* just to start the cookie engine */


            eh.perform(curl);

            curls.push_back(curl);
        }

//        eh.perform(curls);

        sleep(30000);
    }

    curl_global_cleanup();
    
    return EXIT_SUCCESS;
}


