<?php

require_once('frontend.inc.php');

$strings = $queries = $rhits = $hcount = $magic = $value = $sub_magic = $sub_value = Array();
$sub_queries = $sub_rhits = $sub_uhits = Array();
$META_TYPES = $META_TEMP = $SEARCH_TYPES = $SEARCH_TEMP = Array();
$query = '';
$modifier = '';
$random = false;
$date = $date_f = false;

if(isset($_REQUEST['q']) && ctype_digit($_REQUEST['q'])) {
	$MAX_METHOD_ID = max_strip_id();
	if($MAX_METHOD_ID >= $_REQUEST['q'] && $_REQUEST['q'] > 0)
	{
		header('HTTP/1.0 301 Moved Permanently');
		header('Location: ' . SITE_HOST . SITE_PATH . "/strip/$_REQUEST[q]");
		exit(0);
	}
}

#check if it's a date
#try these three different formats
# mm dd yyyy first
$date = strptime($_REQUEST['q'], "%m.%d.%Y");
if(!$date) $date = strptime($_REQUEST['q'], "%m/%d/%Y");
if(!$date) $date = strptime($_REQUEST['q'], "%m-%d-%Y");

#then yyyy mm dd
if(!$date) $date = strptime($_REQUEST['q'], "%Y/%m/%d");
if(!$date) $date = strptime($_REQUEST['q'], "%Y.%m.%d");
if(!$date) $date = strptime($_REQUEST['q'], "%Y-%m-%d");

#if date, convert to unix timestamp
if($date)
{
	$date_formatted = (1900+$date['tm_year']).'-'.(1+$date['tm_mon']).'-'.$date['tm_mday'];
	$date_f = strtotime($date_formatted);
	$date_f = strtotime("+1 day", $date_f);
}

#if unix timestamp, try to go there
if($date_f && $date)
{
	$goto = get_by_date($date_f);
	if(isset($goto) && $goto > 0)
	{
		header('HTTP/1.0 301 Moved Permanently');
		header('Location: ' . SITE_HOST . SITE_PATH . "/strip/$goto");
		exit(0);
	}
}

if(isset($_REQUEST['q']))
{

	if( isset($_GET['method-fp_x']) || isset($_GET['method-fp_y']) 
			|| isset($_GET['method-fp.x']) || isset($_GET['method-fp.y']) ) {
		array_push($magic, 'max');
		array_push($value, $_GET['current'] - 1);
		
		array_push($magic, 'method');
		array_push($value, 'Find Previous');	
	}
	if( isset($_GET['method-fn_x']) || isset($_GET['method-fn_y']) 
			|| isset($_GET['method-fn.x']) || isset($_GET['method-fn.y']) ) {
		array_push($magic, 'min');
		array_push($value, $_GET['current'] + 1);
		
		array_push($magic, 'method');
		array_push($value, 'Find Next');
	}

	// Turn named parameters into query string items
	$_REQUEST['q'] = trim($_REQUEST['q']);
	foreach($_GET as $k => $v) {
		if( $k == 'x' || $k == 'y' ) continue;
		if( $k == 'q' || $k == 'current' ) continue;
		if( substr($k, 0, 7) == 'method-' ) continue;
		if(is_array($v)) {
			foreach($v as $val)
				$_REQUEST['q'] = trim("$_REQUEST[q] $k:\"$val\"");
		} else {
			$_REQUEST['q'] = trim("$_REQUEST[q] $k:\"$v\"");
		}
	}
	
	if(get_magic_quotes_gpc())
		$_REQUEST['q'] = stripslashes($_REQUEST['q']);
	
	$tokens = tpw_parse_line('\s+', $_REQUEST['q']);

	# Protect against SQL injection in the event of Register Globals being on.
	$SEARCH_MIN = $SEARCH_MAX = null;
	
	foreach($tokens as $token) {
		$token = strtolower($token);

		# Determine if we want to subtract
		if($token[0] == '-') {
			$token = substr($token, 1);
			$loop_magic =& $sub_magic;
			$loop_value =& $sub_value;
		} else {
			$loop_magic =& $magic;
			$loop_value =& $value;
		}

		$subtokens = preg_match('/(?:\w+|[#*]):/', $token) ? preg_split('/:/', $token, 2) : Array( false, $token );
	
		# Magic values are easy, just throw them on the list
		array_push($loop_magic, $subtokens[0]);
	
		# Values, however, need a bit more cleaning up first
		if('""' == $subtokens[1])
			array_push($loop_value, '""');
		else
			array_push($loop_value, preg_replace('/[[:punct:]]|(?<=\s)\s+/', '', $subtokens[1]));
	}
	
	#DEBUG
	#print_r($magic);
	#echo "<br>";
	#print_r($value);
	#echo "<br>";

	#do subtraction logic first
	if(count($sub_magic) > 0)
	{
		#iterate over all subtraction magic/value pairs
		foreach($sub_magic as $i => $m)
		{
			$query = "SELECT DISTINCT strip.id FROM strip
					  LEFT JOIN transcript ON strip.id = transcript.strip
					  JOIN strip_t ON strip.type = strip_t.id
					  LEFT JOIN meta ON strip_t.id = meta.type
					  LEFT JOIN meta_t ON meta.meta = meta_t.id
					  WHERE (strip.published < NOW())
					  AND (transcript.speaker NOT LIKE \"#%\"
						OR transcript.speaker IS NULL) ";
		
			$flag = false;
		
			if($m)  #attempt to short-circuit on no magic
			{
				# well, we have magic
				if($m == "type" || $m == "chapter")
				{
					if(ctype_digit(strval($sub_value[$i])))
					{
						$modifier .= " AND (strip_t.description NOT LIKE '%chapter ".mysqli_real_escape_string($link,$sub_value[$i])."%')";
						continue;
					}
					else
					{
						$modifier .= " AND (strip_t.description NOT LIKE '%".mysqli_real_escape_string($link,$sub_value[$i])."%')";
						continue;
					}
				}
				else if($m == "meta")
				{
					$modifier .= " AND (meta_t.name NOT LIKE '".mysqli_real_escape_string($link,$sub_value[$i])."%')";
					continue;
				}
				else if($m == "*")
				{
					#we want to exclude the speaker from this search
					# but here, we don't actually need to do much
					$m = mysqli_real_escape_string($link,$m);
				}
				else
				{
					#it's not a special operator, so assume they mean a character speaking
					$tmp = mysqli_real_escape_string($link,$m);
					$query .= "AND (transcript.speaker LIKE \"%$tmp%\" ) ";
					$flag = true;
				}
			}
		
			#now, handle the $value portion of the string
			# first, handle for ""
			switch($sub_value[$i]) {
				case '""':
					$query .= "AND (transcript.search = '' ) ";
					break;
				case '':
					break;
				case '*':
					$query .= "AND (transcript.search != '' ) ";
					break;
				default:
					$tmp = mysqli_real_escape_string($link,$sub_value[$i]);
					$query .= "AND (transcript.search LIKE \"%$tmp%\"";
					if($m != '*'  && !$flag)
					{
						$query.= "OR transcript.speaker = \"$tmp\"
										OR strip.title LIKE \"%$tmp%\" ";
					}
					$query.=") ";
			}
		
			array_push($sub_queries, $query);
		}
		
		foreach($sub_queries as $q)
		{
			#DEBUG: run the search
			#echo $q;
			$result = mysqli_query($link,$q);
			
			if(!$result)
			{
				continue;
			}
			
			while($row = mysqli_fetch_row($result))
			{
				#and put all the hits onto an array
				array_push($sub_rhits, $row[0]);
			}
		}
	}

	foreach($magic as $i => $m)
	{
		$query = "SELECT DISTINCT strip.id FROM strip
				  LEFT JOIN transcript ON strip.id = transcript.strip
				  JOIN strip_t ON strip.type = strip_t.id
				  LEFT JOIN meta ON strip_t.id = meta.type
				  LEFT JOIN meta_t ON meta.meta = meta_t.id
				  WHERE (strip.published < NOW())
				  AND (transcript.speaker NOT LIKE \"#%\"
					OR transcript.speaker IS NULL) ";
	
		$flag = false;
	
		if($m)  #attempt to short-circuit on no magic
		{
			# well, we have magic
			# so handle special operators first
			# max and min don't really generate queries, so they short-circuit the loop
			if($m == "min")
			{
				if(ctype_digit(strval($value[$i])))
					$SEARCH_MIN = (int)$value[$i];
				continue;
			}
			else if($m == "max")
			{
				if(ctype_digit(strval($value[$i])))
					$SEARCH_MAX = (int)$value[$i];
				continue;
			}
			else if($m == "#")
			{
				if(ctype_digit(strval($value[$i])))
				{
					header('HTTP/1.0 301 Moved Permanently');
					header('Location: ' . SITE_HOST . SITE_PATH . "/strip/$value[$i]");
					exit(0);
				}
				elseif($value[$i] == "random")
				{
					$random = true;
				}
				continue;
			}
			else if($m == "type" || $m == "chapter")
			{
				if(ctype_digit(strval($value[$i])))
				{
					array_push($SEARCH_TYPES, mysqli_real_escape_string($link,"chapter ".$value[$i]));
					continue;
				}
				else
				{
					array_push($SEARCH_TYPES, mysqli_real_escape_string($link,$value[$i]));
					continue;
				}
			}
			else if($m == "meta")
			{
				array_push($META_TYPES, mysqli_real_escape_string($link,$value[$i]));
				continue;
			}
			else if($m == "book")
			{
				if(ctype_digit(strval($value[$i])))
				{
					$BOOK = " AND (strip.book =" . (int)$value[$i] . ") ";
					continue;
				}
			}
			else if($m == "page")
			{
				if(ctype_digit(strval($value[$i])))
				{
					$PAGE = " AND (strip.page =" . (int)$value[$i] . ") ";
					continue;
				}
			}
			else if($m == "method")
			{
				$METHOD = $value[$i];
				continue;
			}
			else if($m == "*")
			{
				#we want to exclude the speaker from this search
				# but here, we don't actually need to do much
				$m = mysqli_real_escape_string($link,$m);
			}
			else
			{
				#it's not a special operator, so assume they mean a character speaking
				$tmp = mysqli_real_escape_string($link,$m);
				$query .= "AND (transcript.speaker LIKE \"%$tmp%\" ) ";
				$flag = true;
			}
		}
	
		#now, handle the $value portion of the string
		# first, handle for ""
		switch($value[$i]) {
			case '""':
				$query .= "AND (transcript.search = '' ) ";
				break;
			case '':
				break;
			case '*':
				$query .= "AND (transcript.search != '' ) ";
				break;
			default:
				$tmp = mysqli_real_escape_string($link,$value[$i]);
				$query .= "AND (transcript.search LIKE \"%$tmp%\"";
				if($m != '*'  && !$flag)
				{
					$query.= "OR transcript.speaker = \"$tmp\"
									OR strip.title LIKE \"%$tmp%\" ";
				}
				$query.=") ";
		}
	
		array_push($queries, $query);
	}
	
	if(count($queries) == 0)
	{
		array_push($queries, $query);
	}

	#additive search logic

	#generate type and metatype strings now
	#searching for a specific type is a little messy
	if(count($SEARCH_TYPES) > 0)
	{
		foreach($SEARCH_TYPES as $SEARCH)
		{
				array_push($SEARCH_TEMP, "strip_t.description LIKE '%$SEARCH%'
					OR strip_t.name LIKE '%$SEARCH%'");
		}
		#second, collapse them into one clause
		$modifier .= " AND (" . implode(" OR ", $SEARCH_TEMP) . ") ";
	}
	
	#now the metatypes, just like the types
	if(count($META_TYPES) > 0)
	{
		#OK, now apply the metatype restrictions...
		foreach($META_TYPES as $META)
		{
			#first, generate the restriction strings
				array_push($META_TEMP, "meta_t.name LIKE '" . $META . "%'");
		}
		#second, collapse them into one clause
		$modifier .= " AND (" . implode(" OR ", $META_TEMP) . ") ";
	}
	
	#now do min and max
	if(isset($SEARCH_MIN))
	{
		$modifier .= "AND ($SEARCH_MIN <= strip.id) ";
	}
	if(isset($SEARCH_MAX))
	{
		$modifier .= "AND (strip.id <= $SEARCH_MAX) ";
	}
	
	#and now book and page
	if(isset($BOOK))
	{
		$modifier .= $BOOK;
	}
	if(isset($PAGE))
	{
		$modifier .= $PAGE;
	}
	
	foreach($queries as $q)
	{
		#apply modifiers
		$q .= $modifier;
		#DEBUG: run the search
		#echo $q;
		if(empty($q)) {
			continue;
		}
		$result = mysqli_query($link,$q);
		
		if(!$result)
		{
			continue;
		}
		
		while($row = mysqli_fetch_row($result))
		{
			#and put all the hits onto an array
			array_push($rhits, $row[0]);
		}
	}

	#so, now all the raw hits are in the same spot
	#now comes The Magic
	
	# if book and page are used, break out
	if(isset($BOOK) && isset($PAGE) && count($rhits) != 0)
	{
		header('HTTP/1.0 301 Moved Permanently');
		header('Location: ' . SITE_HOST . SITE_PATH . "/strip/$rhits[0]");
		exit(0);
	}
	
	#first, reverse sort and uniquify a copy
	rsort($rhits);
	$uhits = array_unique($rhits);
	#do the same for the subtraction hits
	$sub_uhits = array_unique($sub_rhits);
	
	#now... the subtraction!
	$uhits = array_diff($uhits, $sub_uhits);
	
	#if we're redirecting, break out of the normal search logic here
	if(isset($METHOD) && count($uhits) > 0)
	{
		#$METHOD has a value, so we're bustin' outta here!
		if($METHOD == "Find Next")
		{
			$target = end($rhits);
		}
		else if($METHOD == "Find Previous")
		{
			$target = $rhits[0];
		}
		
		//die($METHOD);
		
		header('HTTP/1.0 301 Moved Permanently');
		header('Location: ' . SITE_HOST . SITE_PATH . "/index.php?strip_id=$target" . "&q=" . urlencode($_REQUEST['q']));
		exit(0);
	}
	
	$revhits = array_reverse($rhits);
	#now create a count for each unique hit
	foreach($uhits as $key => $value)
	{
		$first = array_search($value, $rhits);
		$last = array_search($value, $revhits);
		
		$numhits = count($rhits) - $first - $last;
		
		array_push($hcount, $numhits);
	}
	
	if(count($uhits) > 0) {
		if($random)
		{
			$rand = mt_rand(1, count($uhits));
			header('Location: ' . SITE_HOST . SITE_PATH . "/index.php?strip_id=$uhits[$rand]" . "&q=" . urlencode($_REQUEST['q']));
		}
		$qsearch = mysqli_query($link,'SELECT id, title FROM strip WHERE id IN(' . implode(', ', $uhits) . ') GROUP BY id ORDER BY id DESC');
		while($result = mysqli_fetch_array($qsearch))
		{
			$entry = "<li><a href=\"strip/$result[0]\">".str_pad($result[0], 4, 0, STR_PAD_LEFT);
			$entry.= " - ". htmlentities($result[1]) . "</a></li>";
			array_push($strings, $entry);
		}
	
		#now, a clever multisort...
		array_multisort($hcount, SORT_DESC, SORT_NUMERIC,
			$uhits, SORT_DESC, SORT_NUMERIC,
			$strings, SORT_ASC, SORT_STRING);
	}
}

pagehead('search', 'Search');
?>

	<form class="search" action="search.php" method="get"><div>
		<input type="text" name="q" <?php if( $_REQUEST['q'] ) { printf('value="%s"', utfentities($_REQUEST['q']) ); } ?> />
		<input type="submit" value="Search" />
	</div></form>

	<?php if(count($uhits) > 0) {	
		echo '<div class="content"><div><ol class="results">';
		/*$qsearch = mysqli_query($link,'SELECT id, title FROM strip WHERE id IN(' . implode(', ', $uhits) . ')');
		while($result = mysqli_fetch_array($qsearch))
			printf('<li><a href="?strip_id=%d">%s - %s</a></li>',
			       $result[0], str_pad($result[0], 4, 0, STR_PAD_LEFT), $result[1]);*/
		foreach($strings as $str)
		{
			echo $str;
		}
		echo '</ol></div>';
		echo '<div><p class="results"><a href="search_help">Learn to direct search ninjas!</a></p></div></div>';
		
		?>
	<form class="search" action="search.php" method="get"><div>
		<input type="text" name="q" <?php if( $_REQUEST['q'] ) { printf('value="%s"', utfentities($_REQUEST['q']) ); } ?> />
		<input type="submit" value="Search" />
	</div></form> 
		<?php
	} elseif(isset($_REQUEST['q']) && (strlen($_REQUEST['q']) > 0)) {
		echo '<div class="content"><div><p class="results">So sorry. Search ninjas obey without fail, yet find nothing.</p></div>';
		echo '<div><p class="results"><a href="search_help">Learn to direct search ninjas!</a></p></div></div>';
	} else
	{
		echo '<div class="content"><div><p class="results"><a href="search_help">Learn to direct search ninjas!</a></p></div></div>';
	}
	#search_help();
	
	?>

	<?php pagefoot() ?>
</body>
</html>
<?php

function tpw_parse_line($delimiter, $line)
{
	$i = 0;
	$bits = Array();
	$word = '';

	$valid_quotes = '"';

	# $line must not have trailing slash or quote
	$line = preg_replace('/((?:\\\\\\\\)*)\\\\?$/', '\1', rtrim($line));
	if(substr_count($line, '"') % 2 == 1)
		$line .= '"';

	$regex = "/^([$valid_quotes])((?:\\\\.|(?!\\1)[^\\\\])*)\\1([\\000-\\377]*)|" .
	         "^((?:\\\\.|[^\\\\$valid_quotes])*?)(\\Z(?!\\n)|$delimiter|(?!^)(?=[$valid_quotes]))([\\000-\\377]*)/x";

	while (strlen($line) && $i++ < 100) {
		$tokens = preg_match($regex, $line, $matches);
		$tpw_quote = $matches[1];
		$tpw_quoted = $matches[2];
		$tpw_unquoted = $matches[4];
		$tpw_delim = $matches[5];

		if ($tpw_quote || strlen($tpw_unquoted) || strlen($tpw_delim)) { }
		else { return; }

		$line = (isset($matches[6]) ? $matches[6] : $matches[3]);

		//              Remove quotes and backslashes from tokens
		/*              $tpw_unquoted = preg_replace("/\\\\(.)/", '$1', $tpw_unquoted);
		if (isset($tpw_quote)) {
		preg_replace("/\\\\(.)/", '$1', $tpw_quoted);
		} */

		//              Keep quotes and backslashes in tokens
		$tpw_quoted = "$tpw_quote$tpw_quoted$tpw_quote";

		$word .= ($tpw_quote ? $tpw_quoted : $tpw_unquoted);

		if (strlen($tpw_delim)) {
			array_push($bits, $word);
			$word = '';
		}
		if (!strlen($line)) {
			array_push($bits, $word);
		}
	}

	return $bits;
}

function get_by_date($stamp)
{
	global $link;
	$qr = mysqli_query($link,"SELECT MAX(id) FROM strip WHERE published < NOW() AND published < FROM_UNIXTIME($stamp)")  or die(mysqli_error($link));
	return current(mysqli_fetch_row($qr));
}

?>
