#include "ps5/file_node.h"
#include "ps5/normal_response.h"
#include "ps5/error_response.h"
#include "ps5/url_ops.h"

#include <strstream.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>

class file_response : public normal_response {
    // A file_response reads from a regular file.
public:
    file_response(string pathname, bool html);
	// checks: the file named by "pathname" must exist and be readable.

    // overrides
    void body(ostream &o);
    virtual string content_type() = 0;
    virtual bool body_length(int &length);
protected:
    string pathname;
};

class html_file_response : public file_response {
public:
    html_file_response(string n) : file_response(n, true) {}
    virtual string content_type() { return "text/html"; }
};

class text_file_response : public file_response {
public:
    text_file_response(string n) : file_response(n, false) {}
    virtual string content_type() { return "text/plain"; }
};

class dir_response : public normal_response {
public:
    dir_response(string pathname_, string self_name) : normal_response(true)
	{ pathname = pathname_; url = self_name; }

    // overrides
    void body(ostream &o);
protected:
    string url;
    string pathname;
};

file_node::file_node(string prefix_, string name)
    : prefix(prefix_), self_name(name)
{}

http_response *file_node::get(string request, extensions *ext) {
    string pathname = extract_pathname(request);
    if (verify_pathname(pathname)) {
	string path = prefix + "/" + pathname;
	return get_file(path, ext);
    } else {
	strstream ss;
	ss << "The pathname " << pathname << " contains an invalid component.";
	return new error_response(http_response::BAD_REQUEST, ss.str());
    }
}

http_response *file_node::get_file(string path, extensions *ext) {
    struct stat sbuf;
    if (0 > ::stat(path.get_chars(), &sbuf)) {
	strstream ss;
	ss << "The pathname " << path
	   << " does not refer to an existing file";
	return new error_response(http_response::NOT_FOUND, ss.str());
    }
    if (!access_file(path, R_OK)) {
	strstream ss;
	ss << "The pathname " << path << " is unreadable";
	return new error_response(http_response::FORBIDDEN, ss.str());
    }
    if (S_ISREG(sbuf.st_mode)) {
	string ext = extension(path);
	if (ext == "html") {
	    return new html_file_response(path);
	} else {
	    return new text_file_response(path);
	}
    } else
    if (S_ISDIR(sbuf.st_mode)) {
	if (access_file(path + "/index.html", R_OK)) {
	    return new html_file_response(path + "/index.html");
	} else {
	    string last = last_component(path);
	    if (last == "") last = self_name;
	    return new dir_response(path, last);
	}
    } else {
	strstream ss;
	ss << "The file " << path << " is not of an understood type";
	return new error_response(http_response::FORBIDDEN, ss.str());
    }
}

bool file_response::body_length(int &length) {
    struct stat sbuf;
    int result = stat(pathname.get_chars(), &sbuf);
    assert(result >= 0);
    length = int(sbuf.st_size);
    return true;
}

void file_response::body(ostream &o) {
    ifstream i = input_file(pathname);
    char c;
    // should check for errors here...
    while (i.get(c))
	o << c;
}

bool verify_pathname(string p) {
    for (;;) {
	string f = first_component(p);
	if (f.length() == 0) return true;
	if (f[0] == '.') return false;
	p = rest_components(p);
    }
}

file_response::file_response(string pathname_, bool html)
	: normal_response(html) {
    pathname = pathname_;
}

void dir_response::body(ostream &o) {
    DIR *d = opendir(pathname.get_chars());
    struct dirent *de;
    if (!d) {
	o << "The path " << pathname << " is an inaccessible directory\n";
	return;
    }
    o << "<head><title>Directory " << pathname << "</title><p>\n"
         "<body><h2>" << pathname << "</h2><dl>";
    while (de = readdir(d)) {
#ifndef ultrix
	string name = string(de->d_name); // Aack!
#else
	string name = string(de->d_name, de->d_namlen);
#endif
	if (name[0] != '.') {
	    o << "<li><a href=\"" << url << '/' << name << '>' <<
                 name << "</a>\n";
	}
    }
    closedir(d);
    o << "</dl>\n";
}
