#include <string>
#include <iostream>
#include <vector>
#include <queue>
#include <cstdio>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>

#include "httpclient.h"

#define BUF_SIZE 4096

class MyPutRequester : public Pipelined::HttpRequester
{
public:
    MyPutRequester(const std::string& directory)
        : directory_(directory), buf_(NULL)
    {
    }

    virtual ~MyPutRequester()
    {
        delete[] buf_;
    }

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

    ssize_t GetFileSize(FILE *fptr) const;
    void SendFile(FILE *fptr, Pipelined::HttpClient* client) const;
private:
    const std::string& directory_;
    char* buf_;
    std::queue<std::string> files_;
    std::vector<std::string> failed_;
};

// The override to do the PUTs
void MyPutRequester::Run(Pipelined::HttpClient *client)
{
    // Build up a queue of files
    DIR* dir = opendir(directory_.c_str());
    if (dir != NULL) {
        struct dirent* ent;
        while ((ent = readdir(dir))) {
            std::string name = ent->d_name;
            if (name[0] != '.') {
                name = directory_ + '/' + name;
                struct stat statbuf;
                int result = stat(name.c_str(), &statbuf);
                if (buf_ == NULL) {
                    if (statbuf.st_blksize != -1) {
                        // Use the optimum buffer size
                        buf_ = new char[statbuf.st_blksize];
                    }
                    else {
                        std::cerr << "Couldn't get block size" << "std::endl";
                        buf_ = new char[BUF_SIZE];
                    }
                }
                if (result == 0) {
                    if (S_ISREG(statbuf.st_mode)) { // Regular files only
                        files_.push(name);
                    }
                }
                else {
                    std::cerr << "Couldn't stat file " << name << std::endl;
                }
            }
        }
        closedir(dir);
        
        // Send them
        while (!files_.empty()) {
            std::string name = files_.front();
            FILE *fptr = fopen(name.c_str(), "r");
            if (fptr != NULL) {
                std::string uri("/cgi-bin/put/");
                uri += name;
                client->StartRequest("PUT", uri);
                client->SendHeader("Content-Length", GetFileSize(fptr));
                client->EndHeaders();
                SendFile(fptr, client);
                fclose(fptr);
            }
            else {
                std::cerr << "Couldn't open file " << name << std::endl;
            }
            files_.pop();
        }
    }
    else {
        std::cerr << "Couldn't open directory " << directory_ << std::endl;
    }
}

// The override to handle responses
void MyPutRequester::HandleResponse(const std::string& request, const Pipelined::HttpStatus& status,
        const std::multimap<std::string, std::string>& headers, Pipelined::Stream* body)
{
    // Request
    std::cout << request << "\n";
    // Status line
    std::cout << status.version << " " << status.code << " " << status.message << "\n";
    // Headers
    for (std::multimap<std::string, std::string>::const_iterator it = headers.begin(); it != headers.end(); ++it) {
        std::cout << it->first << ": " << it->second << "\n";
    }
    std::cout << "\n";
    // Read the entity body
    char buf[BUF_SIZE];
    while (!body->Eof()) {
        size_t bytes_read = body->Read(buf, BUF_SIZE);
        if (bytes_read > 0) {
            printf("%.*s", bytes_read, buf);
        }
    }
    std::cout << "\n" << std::endl;

    // If the request didn't succeed, add it to the failed vector
    if (status.code > 299) {
        failed_.push_back(request);
    }
}

// The override for finishing
void MyPutRequester::HandleFinish(std::queue<std::string>& requests)
{
    // Requests that did not get a reply
    if (!requests.empty()) {
        std::cout << "The following requests did not receive a reply:\n";
        while (!requests.empty()) {
            std::cout << requests.front() << "\n";
            requests.pop();
        }
    }
    // Requests that failed
    if (!failed_.empty()) {
        std::cout << "The following requests failed:\n";
        for (std::vector<std::string>::const_iterator it = failed_.begin();
                it != failed_.end(); ++it) {
            std::cout << *it << "\n";
        }
    }
    // Files that were not sent
    if (!files_.empty()) {
        std::cout << "The following files were not sent:\n";
        while (!files_.empty()) {
            std::cout << files_.front() << "\n";
            files_.pop();
        }
    }
}

// Helper to find the size of an open file
ssize_t MyPutRequester::GetFileSize(FILE *fptr) const
{
    ssize_t length;
    fseek(fptr, 0L, SEEK_END);
    length = ftell(fptr);
    fseek(fptr, 0L, SEEK_SET);
    return length;
}

// Helper to send the file
void MyPutRequester::SendFile(FILE *fptr, Pipelined::HttpClient* client) const
{
    size_t bytes_read;
    while ((bytes_read = fread(buf_, 1, BUF_SIZE, fptr))) {
        size_t bytes_sent = 0;
        while (bytes_sent < bytes_read) {
            size_t sent = client->Write(buf_ + bytes_sent, bytes_read - bytes_sent);
            if (bytes_sent >= 0) {
                bytes_sent += sent;
            }
        }
    }
}

int main(int argc, char **argv)
{
    std::string directory;
    if (argc > 1) {
        directory = argv[1];
    }
    else {
        directory = ".";
    }
    try {
        MyPutRequester requester(directory);
        Pipelined::HttpClient client("localhost");
        client.Run(&requester);
    }
    catch (std::exception& ex) {
        std::cerr << "Exception: " << ex.what() << std::endl;
    }
    return 0;
}