/* The begining of all includes.  Do NOT put any include before this line */

#include "curl.h"
#include "easy.h"
#include "multicurlhandle.h"
extern "C" {
#include "ares.h"
}
using namespace csm;
#include <vector>
#include <set>

/* The end of all includes.  Do NOT put any include after line */
using namespace std;
using namespace csm;

namespace{
	class Lockable{
	public:
		Lockable::Lockable()
		{
			InitializeCriticalSection(&lock_);
		};

		Lockable::~Lockable()
		{
			DeleteCriticalSection(&lock_);
		};

		bool Lockable::lock()
		{
			EnterCriticalSection(&lock_);
			return true;
		};

		void Lockable::unlock()
		{
			LeaveCriticalSection(&lock_);
		};


	private:
		CRITICAL_SECTION	lock_;
	};

	class Gatekeeper{
	public:
		Gatekeeper(Lockable & lock):lock_(lock){lock.lock();};
		~Gatekeeper(){lock_.unlock();};
	private:
		Lockable & lock_;
	};
	
	const string tokRefresh("refresh");
    const string tokContent("content");
    const string tokSemiColon(";");
    const int maxMetaRefreshTime = 10;
    const string tokUrl("url");
    const string tokQuest("?");

    Lockable curlInitLock;
    static bool curlInitialized = false;
    static string bad_content("bad_content");
    static string content_type("content-type");
    static string::size_type content_type_len = content_type.length();
    Lockable dnsLockable;
    Lockable cookieLockable;

    void shareLock(CURL* handle, curl_lock_data data, curl_lock_access addess, void * userptr)
    {
        Lockable *l = (Lockable *)(userptr);        
        l->lock();
    }

    void shareUnlock(CURL* handle, curl_lock_data data, curl_lock_access addess, void * userptr)
    {
        Lockable * l = (Lockable*)(userptr);        
        l->unlock();
    }

};


class MultiCurlHandleInitializer;
typedef MultiCurlHandleInitializer * RefMultiCurlHandleInitializer;
class MultiCurlHandleInitializer
{
  public:
    static RefMultiCurlHandleInitializer initialize();
    ~MultiCurlHandleInitializer()
    {
        curl_global_cleanup();
    }

  private:
    MultiCurlHandleInitializer()
    {
        curl_global_init(CURL_GLOBAL_ALL);      
    };

    static RefMultiCurlHandleInitializer instance_;
};

RefMultiCurlHandleInitializer MultiCurlHandleInitializer::initialize()
{
    if(!curlInitialized)
    {
        Gatekeeper g(curlInitLock);
        if(!curlInitialized)
        {
            instance_ = new MultiCurlHandleInitializer;
            curlInitialized = true;
        }
    }
    return       instance_;
}

RefMultiCurlHandleInitializer  MultiCurlHandleInitializer::instance_ = RefMultiCurlHandleInitializer(NULL);

namespace {
    size_t WriteMemoryCallbackStdString(void *ptr, size_t size, size_t nmemb, void *data)
    {
        register int realsize = size * nmemb;
        string & contents = *(string *)(data);
        contents.append((char *)ptr,realsize);
        return realsize;
    }

    size_t HeaderCallback(void *ptr, size_t size, size_t nmemb, void *data)
    {
        register int realsize = size * nmemb;
        set<string> & contents = *(set<string> *)(data);
        string temp = (char *)ptr;
        string::size_type pos = temp.find(":");
        if(pos == string::npos)
            return realsize;
        string name  = temp.substr(0, pos);
        string value = temp.substr(pos+2);
        //TODO: make this something inside the response, so we know
        //what's allowable
        if(name == content_type &&!MultiCurlHandle::acceptable(value))
        {
            //resp->status = HTTP_Not_Acceptable;
            return -1;
        }
        //resp->headerSet_.insert(temp);
        contents.insert(temp);
        return realsize;
    }

};

    string MultiCurlHandle::userAgentDefault_  = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)";
    bool   MultiCurlHandle::redirectDefault_   = true;
    int    MultiCurlHandle::maxFollowDefault_  = 7;
    int    MultiCurlHandle::timeoutDefault_    = 10;
    string MultiCurlHandle::cookieFileDefault_ = "C:\\src\\DownloaderTest\\DownloaderTest\\Debug\\cookies";


MultiCurlHandle::~MultiCurlHandle()
{
}

MultiCurlHandle::MultiCurlHandle(bool useAltDNS,
                                 const std::string & alt1,
                                 const std::string & alt2):
userAgent_(userAgentDefault_),
cookieFile_(cookieFileDefault_),
redirect_(redirectDefault_),
maxFollow_(maxFollowDefault_),
active_(false),
timeout_(timeoutDefault_),
useAlternateDNS_(useAltDNS),
alternateDNS1_(alt1),
alternateDNS2_(alt2)
{
    MultiCurlHandleInitializer::initialize();
    share_ = curl_share_init();
    curl_share_setopt(share_, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
    curl_share_setopt(share_, CURLSHOPT_LOCKFUNC, shareLock);
    curl_share_setopt(share_, CURLSHOPT_UNLOCKFUNC, shareUnlock);
    curl_share_setopt(share_, CURLSHOPT_USERDATA, &dnsLockable);    
}

void MultiCurlHandle::cleanup(CurlResponseVec & data)
{
    for(CurlResponseVec::iterator i = data.begin();
        i != data.end();
        ++i)
    {
        curl_easy_cleanup((*i)->handle_);
    }
}

void MultiCurlHandle::addHandles(CURLM * multi, CurlResponseVec & data)
{
    for(CurlResponseVec::iterator i = data.begin();
        i != data.end();
        ++i)
    {
        curl_multi_add_handle(multi, (*i)->handle_);
    }
}


bool MultiCurlHandle::initHandles(const vector<string> & urls,
                                  CurlResponseVec & results)
{
    for(vector<string>::const_iterator i = urls.begin();
        i != urls.end();
        ++i)
    {
        RefCurlResponse r  = new CurlResponse(*i);
        initHandle(r);
        results.push_back(r);
    }
    return true;
}

bool MultiCurlHandle::getRequests(const vector<string> & urls, 
                                  vector<RefCurlResponse> & results)
{
    CURLM *multi;
    int still_running;
    initHandles(urls,results);
    multi = curl_multi_init();
    if(!multi || results.size() != urls.size())
    {
        return false;
    }

    addHandles(multi, results);
   
    while(CURLM_CALL_MULTI_PERFORM ==
          curl_multi_perform(multi, &still_running));
 
    while(still_running) {
        struct timeval timeout;
        int rc; /* select() return code */
 
        fd_set fdread;
        fd_set fdwrite;
        fd_set fdexcep;
        int maxfd;
 
        FD_ZERO(&fdread);
        FD_ZERO(&fdwrite);
        FD_ZERO(&fdexcep);
 
        /* set a suitable timeout to play around with */
        timeout.tv_sec = 1;
        timeout.tv_usec = 0;
 
        /* get file descriptors from the transfers */
        curl_multi_fdset(multi, &fdread, &fdwrite, &fdexcep, &maxfd);
 
        rc = select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout);
 
        switch(rc) {
          case -1:
            /* select error */
            break;
          case 0:
            printf("timeout!\n");
          default:
            /* timeout or readable/writable sockets */
            //printf("perform!\n");
            while(CURLM_CALL_MULTI_PERFORM ==
                  curl_multi_perform(multi, &still_running));
            //printf("running: %d!\n", still_running);
            break;
        }
    }
    curl_multi_cleanup(multi);
    cleanup(results);
    return true;
}

bool MultiCurlHandle::initHandle(RefCurlResponse r)
{
    int aresCode;
    CURLcode res;
    res = curl_easy_init_e(&(r->handle_), &aresCode);

    if (res) 
    {
        return false;
    }
    handles_.push_back(r->handle_);
    curl_easy_setopt(r->handle_, CURLOPT_NOSIGNAL, 1);
    curl_easy_setopt(r->handle_, CURLOPT_USERAGENT, userAgent_.c_str());
    curl_easy_setopt(r->handle_, CURLOPT_TIMEOUT, timeout_);
    curl_easy_setopt(r->handle_, CURLOPT_NOPROGRESS, 1);
    curl_easy_setopt(r->handle_, CURLOPT_WRITEFUNCTION, WriteMemoryCallbackStdString);
    //curl_easy_setopt(r->handle_, CURLOPT_COOKIEJAR, cookieFile_.c_str());
    curl_easy_setopt(r->handle_, CURLOPT_FOLLOWLOCATION, redirect_);
    curl_easy_setopt(r->handle_, CURLOPT_MAXREDIRS, maxFollow_); 
    curl_easy_setopt(r->handle_, CURLOPT_WRITEDATA, (void *)&(r->body));
    curl_easy_setopt(r->handle_, CURLOPT_URL, r->url_.c_str());
    curl_easy_setopt(r->handle_, CURLOPT_HEADERFUNCTION, HeaderCallback);
    curl_easy_setopt(r->handle_, CURLOPT_HEADERDATA,     &(r->headerSet_));
    //curl_easy_setopt(r->handle_, CURLOPT_HEADERDATA,     &r);
    curl_easy_setopt(r->handle_, CURLOPT_SHARE, share_);    
    return true;
}

bool MultiCurlHandle::acceptable(const string & contentType)
{
    if(contentType.find("text/html") == 0||
       contentType.find("text/plain") == 0)
    {
        return true;
    }
    else
    {
        return false;
    }
}

