/**
 * File: subprocess-dual.cc
 * -------------------
 * Presents the implementation of an expanded subprocess function
 * that both allows us to write to the child's STDIN and read from
 * its STDOUT.
 */

#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include "exit-utils.h"
#include "stsh-parser/stsh-parse.h"
#include "fork-utils.h" // this needs to be the last #include in the list

using namespace std;


/**
 * Type: subprocess_t
 * ------------------
 * Bundles information related to the child process created
 * by the subprocess function below.
 * 
 *  pid: the id of the child process created by a call to subprocess
 *  supplyfd: the descriptor where one pipes text to the child's stdin (or 
 *            kNotInUse if child hasn't rewired its stdin)
 *  ingestfd: the descriptor where the text a child pushes to stdout shows
 *            up (or kNotInUse if child hasn't rewired its stdout)
 */
struct subprocess_t {
	pid_t pid;
	int supplyfd;
	int ingestfd;
};

/* Constant residing in a descriptor field within a subprocess_t when
 * the descriptor isn't being used.
 */
static const int kNotInUse = -1;

/**
 * Function: subprocess
 * --------------------
 * Creates a new process running the executable identified via argv[0].
 *
 *   argv: the NULL-terminated argument vector that should be passed
 *         to the new process's main function
 *   supplyChildInput: true if the parent process would like to pipe
 *                     content to the new process's stdin, false otherwise
 *   ingestChildOutput: true if the parent would like the child's stdout
 *                      to be pushed to the parent, false otherwise
 */
subprocess_t subprocess(const char *command, bool supplyChildInput, bool ingestChildOutput) {
	// this line parses the command into a pipeline like is done for you on assign3
	pipeline p(command);

	// Conditionally create pipes based on what I/O we need to redirect
	int pipes[4] = { kNotInUse, kNotInUse, kNotInUse, kNotInUse };
	if (supplyChildInput) pipe(pipes);
	if (ingestChildOutput) pipe(pipes + 2);

	subprocess_t returnValue;
	returnValue.supplyfd = pipes[1];
	returnValue.ingestfd = pipes[2];
	returnValue.pid = fork();

	// If we're the child, rewire our FDs if necessary
	if (returnValue.pid == 0) {
		// Connect first pipe read end to STDIN
		if (supplyChildInput) dup2(pipes[0], STDIN_FILENO);

		// Connect second pipe write end to STDOUT
		if (ingestChildOutput) dup2(pipes[3], STDOUT_FILENO);

		// close all valid file descriptors
		for (int i = 0; i < sizeof(pipes) / sizeof(pipes[0]); i++) {
			if (pipes[i] != kNotInUse) close(pipes[i]);
		}
		
		execvp(p.commands[0].argv[0], p.commands[0].argv);
		cerr << p.commands[0].argv[0] << ": Command not found" << endl;
		exit(1);
	}
	
	if (supplyChildInput) close(pipes[0]);
	if (ingestChildOutput) close(pipes[3]);
	return returnValue;
}

int main(int argc, char *argv[]) {
    // Spawn a child that is running the sort command
	subprocess_t sp = subprocess("/usr/bin/sort", true, true);

    // We would like to feed these words as input to sort
	const char *words[] = {
		"felicity", "umbrage", "susurration", "halcyon", 
		"pulchritude", "ablution", "somnolent", "indefatigable"
	};

    // write each word on its own line to the STDIN of the child sort process
	for (size_t i = 0; i < sizeof(words) / sizeof(words[0]); i++) {
		dprintf(sp.supplyfd, "%s\n", words[i]);
	}

    // Close the write FD to indicate the input is closed
	close(sp.supplyfd);

    // Now we'd like to read the output
	while (true) {
		char buffer[1024];
		ssize_t bytesRead = read(sp.ingestfd, buffer, sizeof(buffer) - 1);
		if (bytesRead == 0) break;
		buffer[bytesRead] = '\0';
		printf("[%s]", buffer);
	}

    // Close the read FD because we are done reading
	close(sp.ingestfd);

    // Wait for the child to finish before exiting
	pid_t pid = waitpid(sp.pid, NULL, 0);
	if (pid == sp.pid) return 0;
	return 1;
}
