#Like IPC::Run, but more concise and guaranteed to work on windows.
package Controller;
use IPC::Open2;
#newController: creates an object to control a new process that is running a command line shell.
sub newController {
	my @params = @_;
	#Default values:
	my $width = 82;
	if(defined $params[0]{width}){$width = $params[0]{width};}
	my $splitter = qr/\n/;
	if(defined $params[0]{splitter}){$splitter = $params[0]{splitter};}
	my $quiet = 0;
	if(defined $params[0]{quiet}){$quiet = $params[0]{quiet};}
	my $onIncoming = \&onIncomingDefault;
	if(defined $params{onIncoming}){$onIncoming = $params[0]{onIncoming};}
	my $self = {
		pid => undef, #process id
		wtr => undef, #writer
		rdr => undef, #reader
		exs => undef, #exit status
		width => $width, #the number of characters the terminal has in one line, or the number of bytes the user wants to handle at a time
		splitter => $splitter, #the characters we want to split the incoming bytes on to push into the cache
		prompt => $params[0]{prompt}, #the prompt the shell gives to let the user know it's ready for new input
		quiet => $quiet, #whether or not the user wants to see the output from the child or handle the output directly
		onIncoming => $onIncoming, #the subroutine reference the user wants to run on each line of incoming data when it is on loud mode
		cache => [""] #the array containing all the split data since the last prompt
	};
	$self->{pid} = open2($self->{rdr}, $self->{wtr}, @{$params[0]{command}}); #This line starts the new process for the child shell.
	bless $self; #You could technically write return bless $self,
	return $self; #but then you couldn't extend it.
}
#onIncomingDefault: the default method to use on each read of shell output
sub onIncomingDefault {
	print $_[0]; #only printing the output is the default thing to do on each read
}
#ctlPmptChng - Controller Prompt Change: changes the prompt and returns the controller
sub ctlPmptChng {
	$_[0]->{prompt} = $_[1];
	return $_[0];
}
#rum - Read Until Match: takes in no parameter, a string, or a regex, and stops reading from the child process when it has found the prompt, exact string, or match in the output respectively.
sub rum {
	my @params = @_;
	#Empty the cache on every prompt because we don't want it to overflow. The user can make there own store.
	@{$params[0]{cache}} = ("");
	my $par;
	my @split;
	#If the prompt to wait for is a regex,
	if(substr($params[0]{prompt}, 0, 4) eq "(?^:"){
		#If the user doesn't want default printing or control over each read, aka is in quiet mode,
		if($params[0]{quiet}){
			#While your still successfully reading from the pipe,
			while(sysread($params[0]{rdr}, $par, $params[0]{width})){
				#Split the incoming bytes into an array based on the current splitter value.
				@split = split $params[0]{splitter}, $par;
				#Append the first value of the split array to the last variable of the cache array.
				${$params[0]{cache}}[$#{$params[0]{cache}}] .= shift @split;
				#Push the rest into the cache.
				push @{$params[0]{cache}}, @split;
				#Check to make sure you haven't just read the prompt your looking for to know when to stop.
				if($par =~ $params[0]{prompt}){last;}
			}
		}else{
			while(sysread($params[0]{rdr}, $par, $params[0]{width})){
				&{$params[0]{onIncoming}}($par);
				@split = split $params[0]{splitter}, $par;
				${$params[0]{cache}}[$#{$params[0]{cache}}] .= shift @split;
				push @{$params[0]{cache}}, @split;
				if($par =~ $params[0]{prompt}){last;}
			}
		}
	}
	#If the prompt to wait for is an exact string,
	else{
		if($params[0]{quiet}){
			while(sysread($params[0]{rdr}, $par, $params[0]{width})){
				@split = split $params[0]{splitter}, $par;
				${$params[0]{cache}}[$#{$params[0]{cache}}] .= shift @split;
				push @{$params[0]{cache}}, @split;
				my $len = length $params[0]{prompt};
				if(substr($par,-$len,$len) eq $params[0]{prompt}){last;}
			}
		}else{
			while(sysread($params[0]{rdr}, $par, $params[0]{width})){
				&{$params[0]{onIncoming}}($par);
				@split = split $params[0]{splitter}, $par;
				${$params[0]{cache}}[$#{$params[0]{cache}}] .= shift @split;
				push @{$params[0]{cache}}, @split;
				my $len = length $params[0]{prompt};
				if(substr($par,-$len,$len) eq $params[0]{prompt}){last;}
			}
		}
	}
	return $params[0];
}
#wrt: WRiTe to child processes STDIN
sub wrt {
	if(not $_[0]->{quiet}){print $_[1];}
	print {$_[0]->{wtr}} $_[1];
	return $_[0];
}
#end: wait for the child process to end, and gracefully close it out
sub end {
	my @params = @_;
	waitpid($params[0]{pid},0);
	$params[0]{exs} = $? >> 8;
	if(not $params[0]{quiet}){
		print "Child process ".$params[0]{pid}." closed with exit status ".$params[0]{exs}.".\n";
	}
	close $params[0]{wtr}; close $params[0]{rdr};
}
1; #This signals that the module has been read and compiled correctly.