/* coding: utf-8; */
#include <assert.h>
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>

#include "config.h"
#include "soundio-util.h"
#include "soundio-demo.h"

void worker_handle_init(WorkerHandle *h)
{
	memset(h, 0, sizeof(*(h)));
	h->_in = h->_out = h->_err = -1;
}

void worker_handle_uninit(WorkerHandle *h)
{
	int i;

	for (i = 0; i < sizeof(h->free_list) / sizeof(h->free_list[0]); i++) {
		free(h->free_list[i]);
		h->free_list[i] = NULL;
	}

	close_nointr(h->_in);
	close_nointr(h->_out);
	close_nointr(h->_err);
	h->_in = h->_out = h->_err = -1;
}

bool worker_handle_push_tofree(WorkerHandle *h, void *addr)
{
	int i;
	int len = sizeof(h->free_list) / sizeof(h->free_list[0]);

	for (i = 0; i < len && h->free_list[i]; i++) {}
	if (i == len)
		return false;

	h->free_list[i] = addr;
	return true;
}

int worker_stop(WorkerHandle *worker)
{
	int r;
	int status;

	if (!worker->pid) /* already stopped */
		return 0;

	r = kill(worker->pid, SIGINT);
	if (r != 0)
		return -errno;

	if (worker->stop_wait > 0)
		while ((r = usleep(worker->stop_wait)) < 0) {
			if (errno != EINTR)
				return -errno;
		}

	while ((r = waitpid(worker->pid, &status,
		worker->stop_wait > 0 ? WNOHANG : 0)) < 0) {
		if (errno != EINTR)
			return -errno;
	}

	if (r == 0) {
		if (worker->stop_wait > 0) {
			r = kill(worker->pid, SIGKILL);
			if (r != 0)
				return -errno;

			while ((r = waitpid(worker->pid, &status, 0)) < 0) {
				if (errno != EINTR)
					return -errno;
			}
		}
		/* else: Not reached! */
	}

	if (WIFEXITED(status))
		worker->status = WEXITSTATUS(status);
	else if (WIFSIGNALED(status))
		worker->status = -WTERMSIG(status);

	/* mark as inactive */
	worker->pid = 0;

	return 0;
}

/* <0: error
 * =0: not stopped
 * =1: stopped
 */
int worker_check_stopped(WorkerHandle *worker)
{
	int r = 0;
	int status;

	if (!worker->pid)
		return 1;

	while ((r = waitpid(worker->pid, &status, WNOHANG)) < 0) {
		if (errno != EINTR)
			return -errno;
	}

	if (r == 0)
		return 0;

	if (WIFEXITED(status))
		worker->status = WEXITSTATUS(status);
	else if (WIFSIGNALED(status))
		worker->status = -WTERMSIG(status);

	/* mark as inactive */
	worker->pid = 0;

	return 1;
}

int record_start(WorkerHandle *worker, const char *sample_path)
{
	int r;
	char *_sample_path = strdup(sample_path);

	if (!_sample_path)
		return -ENOMEM;

	worker->argv[0] = ARECORD;
	worker->argv[1] = "-f";
	worker->argv[2] = "S16_LE";
	worker->argv[3] = "-c";
	worker->argv[4] = "1";
	worker->argv[5] = "-r44100";
	worker->argv[6] = "-t";
	worker->argv[7] = "wav";
	worker->argv[8] = "-D";
	worker->argv[9] = PCM_RI;
	worker->argv[10] = _sample_path;
	worker->argv[11] = NULL;

	assert(worker_handle_push_tofree(worker, _sample_path));

	r = spawn_async_with_pipes(worker->working_directory,
				   worker->argv,
				   worker->envp,
				   SPAWN_SEARCH_PATH | SPAWN_STDOUT_TO_DEV_NULL,
				   &worker->pid,
				   NULL, /* stdin */
				   NULL, /* stdout */
				   &worker->_err);
	if (r == 0)
		assert (fd_nonblock(worker->_err, true) == 0);

	return r;
}

int play_start(WorkerHandle *worker, const char *sample_path)
{
	int r;
	char *_sample_path = strdup(sample_path);

	if (!_sample_path)
		return -ENOMEM;

	worker->argv[0] = APLAY;
	worker->argv[1] = "-i";
	worker->argv[2] = "-D";
	worker->argv[3] = "mono"; /* configured in asoundrc */
	worker->argv[4] = _sample_path;
	worker->argv[5] = NULL;

	assert(worker_handle_push_tofree(worker, _sample_path));

	r = spawn_async_with_pipes(worker->working_directory,
				   worker->argv,
				   worker->envp,
				   SPAWN_SEARCH_PATH | SPAWN_STDOUT_TO_DEV_NULL,
				   &worker->pid,
				   &worker->_in,
				   NULL, /* stdout */
				   &worker->_err);

	if (r == 0)
		assert(fd_nonblock(worker->_err, true) == 0);

	return r;
}

/* Assume: is playing
 * <0: error
 * =0: success
 * =1: worker already stopped
 */
int play_pause(WorkerHandle *worker)
{
	int r;
	void *buf = NULL;
	size_t buf_len = 0;
	const char *cmd = " ";
	const char *reply = "\r=== PAUSE ===                                                            ";

	assert(worker->pid);

	if (worker->status) /* already paused */
		return 0;

	/* eat stderr */
	loop_read_all(worker->_err, &buf, &buf_len, false);
	free(buf);
	r = loop_write(worker->_in, cmd, sizeof(cmd), false);

	if (r < 0 && r != -EPIPE)
		return r;

	buf = NULL;
	r = loop_read_all(worker->_err, &buf, &buf_len, true);
	if (r > 0 && !strstr(buf, reply)) /* Not find the expected reply */
		r = -EREMOTEIO;
	free(buf);

	if (r < 0 && r != -EIO)
		return r;

	r = worker_check_stopped(worker);
	if (r == 0) {
		/* TODO: read stderr to confirm is real stopped */
		worker->status = 1;
	}

	return r;
}

int play_continue(WorkerHandle *worker)
{
	int r;
	void *buf = NULL;
	size_t buf_len = 0;
	const char *cmd = " ";
	const char *reply = "                                                                          \r";

	assert(worker->pid);

	if (!worker->status) /* not paused */
		return 0;

	/* eat stderr */
	loop_read_all(worker->_err, &buf, &buf_len, false);
	free(buf);
	r = loop_write(worker->_in, cmd, sizeof(cmd), false);
	if (r < 0)
		return r;

	buf = NULL;
	r = loop_read_all(worker->_err, &buf, &buf_len, true);
	if (r > 0 && !strstr(buf, reply)) /* Not find the expected reply */
		r = -EREMOTEIO;
	free(buf);

	if (r < 0)
		return r;

	worker->status = 0;
	return 0;
}

int play_stop(WorkerHandle *worker)
{
	play_continue(worker);
	return worker_stop(worker);
}

int mixer_start(WorkerHandle *worker)
{
	int r;

	worker->argv[0] = AMIXER;
	worker->argv[1] = "-c";
	worker->argv[2] = CARD_INDEX;
	worker->argv[3] = "-s";
	worker->argv[4] = NULL;

	r = spawn_async_with_pipes(worker->working_directory,
				   worker->argv,
				   worker->envp,
				   SPAWN_SEARCH_PATH,
				   &worker->pid,
				   &worker->_in,
				   &worker->_out,
				   &worker->_err);

	if (r == 0) {
		assert(fd_nonblock(worker->_err, true) == 0);
		assert(fd_nonblock(worker->_out, true) == 0);
	}

	return r;
}

static int parse_precent(char *line)
{
	char *p = strstr((const char *) line, "%");
	int r = -EINVAL;

	if (p) {
		p--;
		while (p >= line && *p >= '0' && *p <= '9')
			p--;

		p++; 

		errno = 0;
		r = strtol(p, NULL, 10);
		if (errno)
			r = -errno;
	}

	return r;
}

static int parse_vol(int fd, int *left, int *right)
{
	int r;
	int parsed_count = 0;
	void *buf = NULL;

	while (parsed_count < 2) {
		size_t buf_len;
		char *curr_line, *next_line;

		if ((r = loop_read_all(fd, &buf, &buf_len, true)) < 0)
			goto quit;

		if (!buf) {
			r = -EINVAL;
			goto quit;
		}

		curr_line = (char *) buf;
		next_line = strstr(curr_line, "\n");
		while (curr_line) {
			char *_line;

			if (next_line)
				next_line[0] = '\0';
			
			_line = strstrip(curr_line);

			if (startswith(_line, "Front Left: Playback")) {
				r = parse_precent(_line);
				if (r < 0)
					goto quit;

				*left = r;
				parsed_count++;
			} else if (startswith(_line, "Front Right: Playback")) {
				r = parse_precent(_line);
				if (r < 0)
					goto quit;
				*right = r;
				parsed_count++;
			}

			if (!next_line)
				break;

			curr_line = next_line + 1;
			next_line = strstr(curr_line, "\n");
		}

		free(buf);
		buf = NULL;
	}

	r = 0;
quit:
	free(buf);
	return r;
}

int mixer_get_play_vol(WorkerHandle *worker, int *left, int *right)
{
	int r;
	char *cmd = "sget " SCTRL_NAME "\n";
	void *buf = NULL;
	size_t buf_len = 0;
	int _left, _right;

	r = loop_write(worker->_in, cmd, strlen(cmd), false);

	if (r < 0)
		return r;

	/* eat stderr */
	loop_read_all(worker->_err, &buf, &buf_len, false);
	free(buf);

	r = parse_vol(worker->_out, &_left, &_right);

	/* eat stdout */
	loop_read_all(worker->_out, &buf, &buf_len, false);
	free(buf);

	if (r == 0) {
		*left = _left;
		*right = _right;
	}

	return r;
}

int mixer_set_play_vol(WorkerHandle *worker, int *precent)
{
	int r;
	char cmd[100] = { 0, };
	void *buf = NULL;
	size_t buf_len = 0;
	int left, right;

	snprintf(cmd, sizeof(cmd), "sset " SCTRL_NAME "  %d%%\n", *precent);
	r = loop_write(worker->_in, cmd, strlen(cmd), false);

	if (r < 0)
		return r;

	/* eat stderr */
	loop_read_all(worker->_err, &buf, &buf_len, false);
	free(buf);

	r = parse_vol(worker->_out, &left, &right);

	/* eat stdout */
	loop_read_all(worker->_out, &buf, &buf_len, false);
	free(buf);

	if (r == 0)
		*precent = left;

	return r;
}

