#include <config.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>

#ifndef WIN32
#include <unistd.h>
#include <dirent.h>
#include <fcntl.h>
#define DIRECTORY_SEP "/"
#define IS_DIRECTORY_SEP(c) ((c) == '/')
#else
#define stat _stat
#define chdir _chdir
#define S_ISDIR(m) ((m) & _S_IFDIR)
#define S_IRUSR _S_IREAD
#define S_IWUSR _S_IWRITE
#define DIRECTORY_SEP "\\"
#define IS_DIRECTORY_SEP(c) (((c) == '/') || ((c) == '\\'))
#endif

#ifdef HAVE_UNIX_SOCKET
#ifdef HAVE_STDDEF_H
#include <stddef.h>
#endif
#include <sys/un.h>
#endif

#ifndef WIN32
#include <unistd.h>
#include <dirent.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/resource.h>
#else
// TODO
#endif

#if defined(HAVE_POLL)
#include <sys/poll.h>
#elif defined(HAVE_SELECT)
#include <sys/select.h>
#endif


#include "IMUtil.hh"
#include "IMLog.hh"

#ifdef HAVE_TLS
#include <openssl/ssl.h>
#include "IMTLS.hh"
#endif

/*******************************************************************************
                             DirectoryInfo
*******************************************************************************/

bool
DirectoryInfo::addfile(
    const char *filename
)
{
    struct stat s;
    DirectoryEntry d;

    string full = filename;
    if (full.empty()) return false;

    char head = full.at(0);
    if (!IS_DIRECTORY_SEP(head)) {
	full = path;
	char tail = full.at(full.size() - 1);
	if (!IS_DIRECTORY_SEP(tail)) {
	    full = full.append(DIRECTORY_SEP);
	}
	full = full.append(filename);
    }

    if (stat(full.c_str(), &s) != 0) return false;
    if (!subdirp && S_ISDIR(s.st_mode)) return false;
    if (!unreadablep && (!(s.st_mode & S_IRUSR))) return false;

    d.fullname = full;

    {
	string::size_type fpos;
#ifdef WIN32
	fpos = d.fullname.find_last_of("\\/");
#else
	fpos = d.fullname.rfind('/');
#endif
	if (fpos == string::npos) {
	    d.filename = d.fullname;
	    d.dirname = "";
	} else {
	    d.filename = d.fullname.substr(fpos + 1);
	    d.dirname = d.fullname.substr(0, fpos + 1);
	}
    }

    d.readable_p = (s.st_mode & S_IRUSR);
    d.writable_p = (s.st_mode & S_IWUSR);
    d.directory_p = S_ISDIR(s.st_mode); 
    dirvec.push_back(d);

    return true;
}

bool
DirectoryInfo::refresh(
    const char *x_path
)
{
    if (x_path) path = x_path;
    if (path.empty()) return false;

    if (!dirvec.empty()) dirvec.erase(dirvec.begin(), dirvec.end());

#if defined(WIN32)
#error "TODO"
#elif defined(HAVE_READDIR_R)
    DIR *dir;
    struct dirent *entry;
    struct dirent *result;
    int ret;

    dir = opendir(path.c_str());
    if (!dir) return false;
    entry = (struct dirent*) malloc(sizeof(struct dirent) + NAME_MAX);
    if (!entry) return false;
    while (((ret = readdir_r(dir, entry, &result)) == 0)
	   && result) {
	addfile(result->d_name);
    }
    free(entry);
    closedir(dir);
#else /* normal readdir */
    DIR *dir;
    struct dirent *result;
    int ret;

    dir = opendir(path.c_str());
    if (!dir) return false;
    while (result = readdir(dir)) {
	addfile(result->d_name);
    }
    closedir(dir);
#endif

    return true;
}

DirectoryInfo::DirectoryInfo(
    bool include_subdir_p,
    bool include_unreable_p
)
{
    subdirp = include_subdir_p;
    unreadablep = include_unreable_p;
}

/*******************************************************************************
                             File manipulation.
*******************************************************************************/

CARD8BIT*
IM_read_file(
    const char* filename,
    int size
)
{
    size_t rsize;
    FILE *fd;
    CARD8BIT *pr;

    fd = fopen(filename, "rb");
    if (!fd) {
	return NULL;
    }
    pr = (CARD8BIT*) malloc(sizeof(CARD8BIT) * size);
    if (!pr) {
	fclose(fd);
	return NULL;
    }
    rsize = fread(pr, sizeof(CARD8BIT), size, fd);
    fclose(fd);
    if (rsize != size) {
	free(pr);
	return NULL;
    }

    return pr;
}

// return -1 if error occurs.
int IM_file_size(
    const char* filename
)
{
    struct stat s;
    if (stat(filename, &s) != 0) return -1;
    return s.st_size;
}

/*******************************************************************************
			       IMSocket & IMSocketListen
*******************************************************************************/

//
// Address object.
//

void*
IMSocketAddress::get_addrinfo()
{
    struct addrinfo hints, *pst;
    int st;

    if (addrinfo) return addrinfo;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = PF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    if (!address.c_str()) hints.ai_flags = AI_PASSIVE;
    st = getaddrinfo(address.c_str(), service.c_str(), &hints, &pst);
    if (st != 0) {
	LOG_ERROR("Fail to getaddrinfo:%s, %s",
		  address.c_str(), service.c_str());
	return NULL;
    }

    addrinfo = pst;

    return pst;
}

void
IMSocketAddress::bind_fail(
    int fd
)
{
    int e = errno;

    LOG_ERROR("Fail to bind socket: %s:%s", address.c_str(), service.c_str());
#ifdef EADDRINUSE
    const int err_addr_in_use = EADDRINUSE;
#else
    const int err_addr_in_use = EINVAL;
#endif
    if (e == err_addr_in_use) {
	LOG_ERROR("Another IIIMP server might be running(address already in use).");
    }

    close(fd);
}

void
IMSocketAddress::configure_socket(
    int fd
)
{
    int reuse = 1;
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
}

int
IMSocketAddress::setup_inet(
    void *paddressinfo
)
{
    struct addrinfo *pai = (struct addrinfo*) paddressinfo;

    int fd = socket(pai->ai_family, pai->ai_socktype, pai->ai_protocol);

    if (fd < 0) {
	LOG_ERROR("Cannot open socket for %s:%s",
		  address.c_str(), service.c_str());
	return -1;
    }

    configure_socket(fd);

    if (bind(fd, pai->ai_addr, pai->ai_addrlen) < 0) {
	bind_fail(fd);
	return -1;
    }

    return fd;
}

int
IMSocketAddress::setup_unix()
{

#if defined(HAVE_UNIX_SOCKET)
    struct stat st;

    memset(&st, 0, sizeof(st));
    if (lstat(address.c_str(), &st) == 0) {
	if (S_ISDIR(st.st_mode) && (st.st_mode & (S_IXUSR | S_IWUSR))
	    && !S_ISLNK(st.st_mode)) {
	} else {
	    return -1;
	}
    } else {
        mode_t mode = (type == UNIX_DOMAIN) ? 0755 : 0700;
        string dir = address.substr(0, address.find(".iiimp-unix", 0));

	if (lstat(dir.c_str(), &st) == 0) {
	    if (S_ISDIR(st.st_mode) && (st.st_mode & (S_IXUSR | S_IWUSR))
		&& !S_ISLNK(st.st_mode)) {
		/* just try to make address directory */
		if (mkdir(address.c_str(), mode) < 0) {
		    return -1;
		}
	    } else {
		return -1;
	    }
	} else {
	    /* base directory like /var/run/iiim is missing. */
	    if (!mkdir(dir.c_str(), mode)) {
		if (mkdir(address.c_str(), mode) < 0) {
		    return -1;
		}
	    } else {
		return -1;
	    }
	}
    }

    int fd = socket(AF_UNIX, SOCK_STREAM, 0);

    if (fd == -1) {
	LOG_ERROR("Cannot open socket. (%s)", address.c_str());
	return false;
    }

    configure_socket(fd);

    struct sockaddr_un sun_addr;

    snprintf(sun_addr.sun_path, sizeof(sun_addr.sun_path), "%s/%s",
	     address.c_str(), service.c_str());
    sun_addr.sun_path[sizeof(sun_addr.sun_path) -1] = '\0';
    sun_addr.sun_family = AF_UNIX;

    size_t size = (offsetof(struct sockaddr_un, sun_path)
	+ strlen (sun_addr.sun_path) + 1);

    if (unlink(sun_addr.sun_path) < 0) {
	if (errno != ENOENT) {
		bind_fail (fd);
		return -1;
	}
    }

    mode_t new_mask = (type == UNIX_DOMAIN) ? 0 : 077;
    mode_t old_mask = umask(new_mask);
    if (bind(fd, (struct sockaddr*) &sun_addr, size) < 0) {
	bind_fail(fd);
	unlink(sun_addr.sun_path);
	return -1;
    }
    umask(old_mask);

    return fd;
#else
    return -1;
#endif
}

int
IMSocketAddress::create_sockets(
    IMSocketVec &sockvec
)
{
    if (type == INET) {
	int i = 0;
	int fd;
	struct addrinfo *pai = (struct addrinfo*) get_addrinfo();
	IMSocket *ps;

	for (; pai; pai = pai->ai_next) {
	    fd = setup_inet(pai);
	    if (fd < 0) continue;
	    ps = new IMSocket(fd, trans_type);
	    // it should be dealt with memory exception.
	    if (!ps) {
		close(fd);
		return -1;
	    }
	    sockvec.push_back(ps);
	    i++;
	}

	if (i == 0) {
	    LOG_CRITICAL("Failed to create any sockets for %s:%s ... exit.",
			 address.c_str(), service.c_str());
	    exit(255);
	}

	return i;
    } else if (type == UNIX_DOMAIN || type == UNIX_DOMAIN_PER_USER) {
	IMSocket *ps;
	int fd = setup_unix();

	if (fd >= 0) {
	    ps = new IMSocket(fd, trans_type);
	    if (!ps) {
		close(fd);
		return -1;
	    }
	}
	if (fd < 0) {
	    LOG_CRITICAL("Failed to create a unix domain socket for %s:%s ... exit.",
			 address.c_str(), service.c_str());
	    exit(255);
	}

	sockvec.push_back(ps);
	return 1;
    }

    ERROR_INTERNAL("invalid IMSocketAddress type");

    return 0;
}

IMSocketAddress::~IMSocketAddress()
{
    if (addrinfo) {
	freeaddrinfo((struct addrinfo*) addrinfo);
    }
#if defined(HAVE_UNIX_SOCKET)
    if (type == UNIX_DOMAIN || type == UNIX_DOMAIN_PER_USER) {
        sockaddr_un sun_addr;
        snprintf(sun_addr.sun_path, sizeof(sun_addr.sun_path), "%s/%s",
	     address.c_str(), service.c_str());
        sun_addr.sun_path[sizeof(sun_addr.sun_path) -1] = '\0';
        unlink (sun_addr.sun_path);
    }
#endif
}

IMSocketAddress::
IMSocketAddress(
    ADDRESS_TYPE x_type,
    const string &x_address,
    const string &x_service
) : trans_type(NORMAL), type(x_type), address(x_address), service(x_service)
{
    addrinfo = NULL;
}

IMSocketAddress::
IMSocketAddress(
    ADDRESS_TYPE x_type,
    TRANSPORT_TYPE x_trans,
    const string &x_address,
    const string &x_service
) : trans_type(x_trans), type(x_type), address(x_address), service(x_service)
{
    addrinfo = NULL;
}

//
// Socket object.
//

bool
IMSocket::do_listen()
{
    if (::listen(fd, 5) < 0) {
	LOG_ERROR("Fail to listen:%d", fd);
	close(fd);
	status = ERROR;
	return false;
    }

    status = OPEN;

    return true;
}

bool
IMSocket::listen()
{
    if (status == ERROR) return false;
    if (status == OPEN) return true;

    return do_listen();
}

IMSocketTrans*
IMSocket::accept()
{
    int st;
#ifdef HAVE_SOCKADDR_STORAGE
    struct sockaddr_storage ss;
#else
    struct sockaddr ss;
#endif
    struct sockaddr *psa = (struct sockaddr*) &ss;
    socklen_t fromlen;

    if (status == ERROR) return NULL;

    do {
	fromlen = sizeof(ss);
	st = ::accept(fd, psa, &fromlen);
	if (st != -1) {
#ifdef HAVE_TLS
            return IMTLS::get_instance()->create_trans(st, trans_type);
#else
	    return new IMSocketTrans(st);
#endif
	}
    } while (errno == EINTR);

    status = ERROR;
    LOG_ERROR("Could not accept.\n");

    return NULL;
}

int
IMSocket::get_fd()
{
    if (status == OPEN) return fd;
    return -1;
}

IMSocket::IMSocket(
    int x_fd
) : fd(x_fd), status(CLOSED), trans_type(IMSocketAddress::NORMAL)
{
}

IMSocket::~IMSocket()
{
    if (status == OPEN) {
	close(fd);
    }
}

IMSocket::IMSocket(
    int x_fd,
    int x_trans
) : fd(x_fd), status(CLOSED), trans_type(x_trans)
{
}

//
// Trans object.
//

int
IMSocketTrans::send(
    const void *p,
    size_t n
)
{
    return ::send(fd, p, n, 0);
}

int
IMSocketTrans::recv(
    void *p,
    size_t n
)
{
    return ::recv(fd, p, n, 0);
}

IMSocketTrans::IMSocketTrans(
    int x_fd
)
{
    fd = x_fd;
}

IMSocketTrans::~IMSocketTrans()
{
    if (fd > 0) {
	shutdown(fd, SHUT_RDWR);
	close(fd);
    }
}


//
// Listen object.
//

bool
IMSocketListen::listen(
    IMSocketAddressVec& endvec
)
{
    if (status == IMSocket::ERROR) return false;
    if (status == IMSocket::OPEN) return true;

    IMSocketAddressVec::iterator ait;
    for (ait = endvec.begin(); ait!= endvec.end(); ait++) {
	if (ait->create_sockets(sockvec) < 0) return false;
    }

    IMSocketVec::iterator it;
    IMSocket *pims;
    for (it = sockvec.begin(); it != sockvec.end(); it++) {
	pims = *it;
	if (pims->get_status() == IMSocket::ERROR) return false;
	if (pims->get_status() == IMSocket::OPEN) continue;
	if (!pims->listen()) {
	    status = IMSocket::ERROR;
	    return false;
	}
    }
    status = IMSocket::OPEN;

    return true;
}

IMSocketTrans*
IMSocketListen::accept()
{
    IMSocketVec::iterator it;
    IMSocket *pims;

    if (status != IMSocket::OPEN) return NULL;

#if defined(HAVE_POLL)
    int fd, i, n;
    struct pollfd *fds;

    i = sockvec.size();
    if (i <= 0) return NULL;
    fds = new struct pollfd[i];
    if (!fds) return NULL;
    for (n = 0, it = sockvec.begin(); it != sockvec.end(); it++) {
	pims = *it;
	fd = pims->get_fd();
	if (fd < 0) continue;
	fds[n].fd = fd;
	fds[n].events = POLLIN;
	fds[n].revents = 0;
	n++;
    }
    if (n > 0) {
	i = poll(fds, n, -1);
	if (i > 0) {
	    for (i = 0; i < n; i++) {
		if (fds[i].revents) {
		    fd = fds[i].fd;
		    for (it = sockvec.begin(); it != sockvec.end(); it++) {
			pims = *it;
			if (fd == pims->get_fd()) {
			    delete [] fds;
			    return pims->accept();
			}
		    }
		}
	    }
	}
    }
    delete [] fds;

#elif defined(HAVE_SELECT)
    int fd, maxfd;
    fd_set fds;
    FD_ZERO(&fds);

    maxfd = -1;
    for (it = sockvec.begin(); it != sockvec.end(); it++) {
	pims = *it;
	fd = pims->get_fd();
	if (fd < 0) continue;
	if (fd > maxfd) maxfd = fd;
	FD_SET(fd, &fds);
    }
    if (maxfd < 0) return NULL;
    maxfd++;
    fd = select(maxfd, &fds, NULL, NULL, NULL);
    if (fd <= 0) return NULL;
    for (it = sockvec.begin(); it != sockvec.end(); it++) {
	pims = *it;
	fd = pims->get_fd();
	if (fd < 0) continue;
	if (FD_ISSET(fd, &fds)) {
	    fd = pims->accept();
	    if (fd < 0) return NULL;
	    return fd;
	}
    }
#else
#error "We need poll() or select() to multiplex socket handles"
#endif

    return NULL;
}

IMSocketListen::IMSocketListen()
{
    status = IMSocket::CLOSED;
}

IMSocketListen::~IMSocketListen()
{
    delete_all(sockvec);
}

/*******************************************************************************
                    IMDaemon (helper object to make a program a daemon)
*******************************************************************************/

IMDaemon* IMDaemon::pimdaemon = NULL;

IMDaemon::IMDaemon()
{
    already_finished = false;
}

IMDaemon*
IMDaemon::get_instance()
{
    if (pimdaemon) return pimdaemon;
    pimdaemon = new IMDaemon();
    return pimdaemon;
}

void
IMDaemon::cleanup()
{
    if (pimdaemon) delete pimdaemon;
}

void
IMDaemon::closefd()
{
    int fd;
#ifdef HAVE_SYSCONF
    int fdmax = sysconf(_SC_OPEN_MAX);
#else
    int fdmax = 3;
#endif
    for (fd = 0; fd < fdmax; fd++) {
	close(fd);
    }

#ifndef WIN32
    // 0 : discard stdout
    open("/dev/null", O_RDWR);
    // 1 : discard stdin
    dup(0);
    // 2 : discard stderr
    dup(0);
#endif
}

bool
IMDaemon::discard_parent()
{
#ifdef HAVE_FORK
    pid_t pid = fork();
    if (pid != 0) {
	if (pid == -1) {
	    return false;
	} else {
	    _exit(0);
	}
    }
#endif
    return true;
}

bool
IMDaemon::daemonize()
{
    if (!discard_parent()) return false;
#ifdef HAVE_SETSID
    if (setsid() < 0)
	return false;
#endif
    if (!discard_parent()) return false;

    return true;
}

void
IMDaemon::setup(
    const char* todir
)
{
    if (!already_finished) {
	if (!daemonize()) {
	    LOG_ERROR("Fail to make a daemon.");
	}
	closefd();
    }
    if (todir) {
	chdir(todir);
    }
}

/*******************************************************************************
                 IMKeyParser (helper object to parse key symbols
*******************************************************************************/
IMKeyParser::IMKeyParser(const char *strings)
{
    char *str, *ptop, *pend;
    IMKeySpec *key;
    string s;

    ptop = (char *) strings;
    while (ptop) {
	if ((pend = strchr(ptop, ',')) != NULL) {
	    s.assign(ptop, pend - ptop);
	    ptop = pend + 1;
	} else {
	    s.assign(ptop);
	    ptop = NULL;
	}
	key = parse_key(s);
	if (key) {
	    keys.push_back(*key);
	}
    }
}

IMKeyParser::~IMKeyParser()
{
}

IMKeySpec *
IMKeyParser::parse_key(
    string s
)
{
    IMKeySpec *key;
    string token, mtoken;
    int j, pos, size, mod = 0;
    static struct {
	const char *symbol;
	int modifier;
    } modifiers[] = {
	    {"shift", 1}, {"sh", 1}, {"s", 1},
	    {"control", 2}, {"ctrl", 2}, {"c", 2},
	    {"alt", 4},
	    {NULL, 0},
    };
#include "keysyms.h"

    size = s.size();
    for (j = 0; j < size; j++) {
	if (s[j] == '<') {
	    pos = s.find('>', j + 1);
	    if (pos > j) {
		mtoken = s.substr(j + 1, pos - j - 1);
		for (int i = 0; modifiers[i].symbol != NULL; i++) {
		    if (!strcasecmp(modifiers[i].symbol, mtoken.c_str())) {
			mod |= modifiers[i].modifier;
			j = pos;
		    }
		}
		if (pos > j) {
		    LOG_DEBUG("unknown modifier symbol: %s\n", mtoken.c_str());
		}
	    }
	} else {
	    if (token.size() > 0) {
		LOG_DEBUG("duplicated key definition. ignoring a previous key(%s)\n", token.c_str());
	    }
	    pos = s.find('<', j + 1);
	    if (pos > j) {
		token = s.substr(j, pos - j);
		j = pos - 1;
	    } else {
		token = s.substr(j);
		break;
	    }
	}
    }
    if (token.size() > 0) {
	for (j = 0; keysymtable[j].keyname != NULL; j++) {
	    if (!strcasecmp (keysymtable[j].keyname, token.c_str())) {
		/* FIXME: xkb's keycode and IIIMF's keycode seems different. */
		key = new IMKeySpec(keysymtable[j].keysym, keysymtable[j].keysym, mod, 0);
		return key;
	    }
	}
	LOG_DEBUG("cannot parse the unknown key(%s)\n", token.c_str());
    }

    return NULL;
}

string
IMKeyParser::generate_key(IMKeySpec &spec)
{
    int keycode = spec.get_keycode();
    int modifier = spec.get_modifier();
    string key;
    static struct {
	const char *symbol;
	int modifier;
    } modifiers[] = {
	{"Shift", 1}, {"Control", 2}, {"Alt", 4}, {NULL, 0}
    };
#include "keysyms.h"

    for (int i = 0; modifiers[i].symbol != NULL; i++) {
	if (modifier & modifiers[i].modifier) {
	    key += string("<") + string(modifiers[i].symbol) + string(">");
	}
    }
    for (int j = 0; keysymtable[j].keyname != NULL; j++) {
	if (keysymtable[j].keysym == keycode) {
	    key += string(keysymtable[j].keyname);
	    return key;
	}
    }
    key = "";

    return key;
}

/* Local Variables: */
/* c-file-style: "iiim-project" */
/* End: */
