#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <glob.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>

#include "sftp.h"

extern sftp_channel *channel[MAXCHANNEL];

#define START 0
#define WAITING_FOR_OK 1
#define SENDING_INFO 3
#define SENDING 4
#define DONE 5

static int sock = 1;

typedef struct {
	int file;
	glob_t globbuf;
	int globerr, fd, size, count, state;
	u_int16_t mode;
} sendfile_data;

int sendfile_accept_data(sftp_channel_t c, message m);
int sendfile_send_data(sftp_channel_t c);

sftp_channel_t
new_sendfile_channel(char *name) {
	sftp_channel_t c;
	sendfile_data *data;
	for (c = 1; c < MAXCHANNEL; c++)
		if (channel[c] == NULL)
			break;
	if (c == MAXCHANNEL)
		return -1;
	channel[c] = (sftp_channel *) malloc(sizeof(sftp_channel));
	memset (channel[c], 0, sizeof(sftp_channel));
	channel[c]->accept_data = sendfile_accept_data;
	channel[c]->send_data = sendfile_send_data;
	channel[c]->data = malloc(sizeof(sendfile_data));
	memset (channel[c]->data, 0, sizeof(sendfile_data));
	data = channel[c]->data;

	data->globerr = glob(name, 0, NULL, &data->globbuf);
	channel[c]->waiting_to_send = 1;
	return c;
}

int
sendfile_accept_data(sftp_channel_t c, message m) {
	sendfile_data *data = channel[c]->data;
	switch (m.command) {
		case FILEOK:
			if (data->state == WAITING_FOR_OK) {
				data->state = SENDING_INFO;
				channel[c]->waiting_to_send = 1;
			}
			else
				return -1;
			break;
		case ERROR:
			return -1;
		default:
			return -1;
	}
	return 0;
}

int
sendfile_send_data(sftp_channel_t c) {
	sendfile_data *data = channel[c]->data;

	if (data->state == START) {
		struct stat statbuf;
		char *base, *name;

		if (data->globerr != 0) {
			char *str = "no matching file";
			send_message(sock, _message(NOFILEMATCH, str,
						    strlen(str) + 1));
			channel[c]->waiting_to_send = 0;
			data->state = DONE;
			return -1;
		}

		name = data->globbuf.gl_pathv[data->file];
		if (name == NULL) {
			char *str = "no matching file";
			send_message(sock, _message(NOFILEMATCH, str,
						    strlen(str) + 1));
			channel[c]->waiting_to_send = 0;
			data->state = DONE;
			return -1;
		}
		if (stat(name, &statbuf) || !S_ISREG(statbuf.st_mode)) {
			char *str = "not a file";
			send_message(sock, _message(NOFILEMATCH, str,
						    strlen(str) + 1));
			channel[c]->waiting_to_send = 0;
			data->state = DONE;
			return -1;
		}
		data->fd = open(name, O_RDONLY);
		if (data->fd < 0) {
			char *str = "cannot open file";
			send_message(sock, _message(NOFILEMATCH, str,
						    strlen(str) + 1));
			channel[c]->waiting_to_send = 0;
			data->state = DONE;
			return -1;
		}
		base = findbasename(name);
		data->mode = statbuf.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO);
		data->size = statbuf.st_size;
		send_message(sock, _message(SENDFILE, &c, 1));
		send_message(sock, _data_message(c, FILENAME, base,
						 strlen(base)+1));
		data->state = WAITING_FOR_OK;
	}
	else if (data->state == SENDING_INFO) {
		u_int32_t nsize;
		char modestr[9];

		nsize = htonl(data->size);
		modetostr(data->mode, modestr);
		send_message(sock, _data_message(c, FILESIZE, &nsize, 4));
		send_message(sock, _data_message(c, FILEMODE, modestr, 9));
		data->state = SENDING;
		channel[c]->waiting_to_send = 1;
	}
	else if (data->state == SENDING) {
		int n;
		char buf[BUFSIZE];

		n = read(data->fd, buf, BUFSIZE);
		if (n > 0) {
			send_message(sock, _data_message(c, DATA, buf, n));
			data->count += n;
		}
		else if (n == 0) {
			send_message(sock, _data_message(c, ENDDATA, NULL, 0));
			close(data->fd);
			data->count = 0;
			data->file++;
			data->state = START;
		}
		else
			return -1;
	}
	return 0;
}

void
close_sendfile_channel(sftp_channel_t c) {
	sendfile_data *data = channel[c]->data;
	globfree(&data->globbuf);
	free(data);
	free(channel[c]);
	channel[c] = NULL;
}
