//
//  httpclient.h - a pipelined HTTP client
//  Copyright (C) 2010 Martin Broadhurst
//  www.martinbroadhurst.com
//

#ifndef HTTPCLIENT_H
#define HTTPCLIENT_H

#include <string>
#include <map>
#include <queue>
#include <sstream>
#include <pthread.h>
#include <signal.h>

namespace Pipelined
{

// Base class for exceptions
class Exception : public std::exception
{
public:
    Exception(const std::string& what)
        : what_(what)
    {
    }

    virtual ~Exception() throw ()
    {
    }

    virtual const char * what () const throw ()
    {
        return what_.c_str();
    }
private:
    std::string what_;
};

struct DisconnectedException
{
};

// Abstract base class for streams representing an entity body
class Stream
{
public:
    virtual ~Stream()
    {
    }

    virtual ssize_t Read(void* buf, size_t size) = 0;
    virtual bool Eof() = 0;
    virtual bool Failed() = 0;
};

// Struct representing an HTTP response status line
struct HttpStatus
{
    std::string version;
    unsigned int code;
    std::string message;
    HttpStatus(const std::string& start);
};

class HttpClient;

// Abstract base class for HTTP requesters
class HttpRequester
{
public:
    virtual ~HttpRequester()
    {
    }

    virtual void Run(HttpClient* client) = 0;
    virtual void HandleResponse(const std::string& request, const HttpStatus& status,
            const std::multimap<std::string, std::string>& headers, Stream* body) = 0;
    virtual void HandleFinish(std::queue<std::string>& requests)
    {
    }
};

// The pipelined client
class HttpClient
{
public:
    HttpClient(const std::string& host,
            const std::string& port = "80");
    ~HttpClient();
    void Run(HttpRequester* requester);
    void StartRequest(const std::string& method, const std::string& uri);

    template <class Value>
    void SendHeader(const std::string& name, const Value& value) const
    {
        std::ostringstream oss;
        oss << name << ": " << value << "\n";
        ssize_t bytes_written = write(fd_, oss.str().c_str(), oss.str().length());
        if (bytes_written == -1) {
            throw DisconnectedException();
        }  
    }

    void EndHeaders() const
    {
        ssize_t bytes_written = write(fd_, "\n", 1);
        if (bytes_written == -1) {
            throw DisconnectedException();
        }
    }

    ssize_t Write(const void* buf, size_t size) const
    {
        ssize_t bytes_written = write(fd_, buf, size);
        if (bytes_written == -1) {
            throw DisconnectedException();
        }
        return bytes_written;
    }

    void Close()
    {
        close(fd_);
    }

private:
    static std::multimap<std::string, std::string> ReadHeaders(HttpClient *client,
            size_t& content_length, bool& chunked);
    static void* Read(void* pclient);
private: // Not copyable
    HttpClient(const HttpClient& other);
    HttpClient& operator=(const HttpClient& other);
private:
    HttpRequester* requester_;
    std::string host_;
    std::string port_;
    int fd_;
    pthread_t reader_;
    std::queue<std::string> requests_;
    pthread_mutex_t request_lock_;
    bool disconnected_;
    sighandler_t old_handler_;
};

class HttpClientException : public Exception
{
public:
    HttpClientException(const std::string& what)
        : Exception(what)
    {
    }
};


};

#endif // HTTPCLIENT_H