<?php

define( 'DEBUG_LEVEL', 1 );
define( 'UPLOAD_PATCH', false );

define( 'WPORG_USER', '' );
define( 'WPORG_PASSWORD', '' );

// The massive first auto-fixer commit.
$firstFixerCommit = '8f95800d52c1736d651ae6e259f90ad4a0db2c3f';

// Subsequent fixer commits.
$fixerCommits = array(
	// Upgrade WPCS to 1.0.0.
	'a75d153eeeac9154c2576697adf721027e1d073e',
);

/*
 * When applying patches, certain strings in the patch will cause the value of the -p option
 * to change. As most patches are made with SVN, we default to 0, and change for other patch styles.
 */
$pLevels = array(
	1 => array(
		'diff --git a/',
		'--- /www/wp-',
	),
);

/*
 * Sometimes patches are uploaded that don't apply to the base directory, so we need to change
 * the --directory option to a new target.
 */
$targets = array(
	'src' => array(
		'Index: wp-',
		'diff --git wp-',
		'diff --git a/wp-',
		'--- /www/wp-',
	),
	'src/wp-includes' => array(
		'Index: feed',
	),
);

if ( ! file_exists( '../working/ticket-data.csv' ) ) {
	echo "Downloading ticket data...\n";

	shell_exec( 'curl https://core.trac.wordpress.org/report/1?sort=ticket&asc=1&format=csv > ../working/ticket-data.csv' );
}

// Make sure master is up to date
$output = shell_exec( 'git checkout -f master' );
if ( DEBUG_LEVEL ) {
	echo $output;
}
$output = shell_exec( 'git pull origin master' );
if ( DEBUG_LEVEL ) {
	echo $output;
}

$ticketHandle = fopen( '../working/ticket-data.csv', 'r' );

while ( ( $ticketData = fgetcsv( $ticketHandle ) ) !== FALSE ) {
	$ticket = $ticketData[0];
	if ( ! is_numeric( $ticket ) ) {
		continue;
	}

	$output = shell_exec( 'git checkout -f master' );
	if ( DEBUG_LEVEL ) {
		echo $output;
	}

	$output = shell_exec( 'git clean -fd' );
	if ( DEBUG_LEVEL ) {
		echo $output;
	}

	$output = shell_exec( 'git reset --hard origin/master' );
	if ( DEBUG_LEVEL ) {
		echo $output;
	}

	if ( file_exists( "../working/$ticket-automerged.diff" ) ) {
		continue;
	}

	if ( ! file_exists( "../working/$ticket.diff" ) ) {
		$ticketHtml = file_get_contents( "https://core.trac.wordpress.org/ticket/$ticket" );

		$matches = array();
		preg_match_all( "|<a href=\"/(attachment/ticket/$ticket/[^\"]*)\" title=\"View attachment\">.*from=([^&]*)|", $ticketHtml, $matches );

		$newestDiff = '';
		$newestDiffTime = 0;
		for ( $ii = 0; $ii < count( $matches[0] ); $ii++ ) {
			if ( ! preg_match( '/.(diff|patch)$/', $matches[1][ $ii ] ) ) {
				continue;
			}

			$time = strtotime( urldecode( $matches[2][ $ii ] ) );
			if ( $time > $newestDiffTime ) {
				$newestDiff = $matches[1][ $ii ];
				$newestDiffTime = $time;
			}
		}

		if ( ! $newestDiffTime || $newestDiffTime >= strtotime( '2017-12-01 00:00:00' ) ) {
			continue;
		}

		shell_exec( "curl https://core.trac.wordpress.org/raw-$newestDiff > ../working/$ticket.diff" );
	}

	// git branch at the auto-fixer commit.
	echo "Creating branch for Ticket #$ticket\n";
	$output = shell_exec( "git branch -f $ticket $firstFixerCommit" );
	if ( DEBUG_LEVEL ) {
		echo $output;
	}
	$output = shell_exec( "git checkout $ticket" );
	if ( DEBUG_LEVEL ) {
		echo $output;
	}

	$diff = file_get_contents( "../working/$ticket.diff" );
	$pLevel = 0;
	foreach ( $pLevels as $level => $testStrings ) {
		foreach ( $testStrings as $testString ) {
			if ( strpos( $diff, $testString ) !== false ) {
				$pLevel = $level;
				break 2;
			}
		}
	}

	$target = '';
	foreach ( $targets as $dir => $testStrings ) {
		foreach ( $testStrings as $testString ) {
			if ( strpos( $diff, $testString ) !== false ) {
				$target = "--directory $dir";
				break 2;
			}
		}
	}

	// Find files changed in the patch.
	echo "Finding files that will be changed by this patch:\n";
	$fileString = shell_exec( "git apply --numstat -p$pLevel ../working/$ticket.diff $target | awk '{print $3}'" );
	echo $fileString;

	$files = explode( "\n", $fileString );
	$fileParams = implode( ' ', $files );

	// Undo changed files to the change before the auto-fixer commit, if those files exist.
	$existingFiles = array_filter( $files, 'file_exists' );
	if ( $existingFiles ) {
		echo "Reverting patch files to previous commit.\n";
		$output = shell_exec( "git checkout $firstFixerCommit~1 -- " . implode( ' ', $existingFiles ) );
		if ( DEBUG_LEVEL ) {
			echo $output;
		}
	}

	// Apply the patch file.
	echo "Applying patch.\n";
	$output = shell_exec( "git apply -p$pLevel ../working/$ticket.diff $target 2>&1" );
	if ( DEBUG_LEVEL ) {
		echo $output;
	}

	if ( strpos( $output, 'error: ' ) !== false ) {
		continue;
	}

	// Install original WPCS/PHPCS versions (source: https://github.com/WordPress/wordpress-develop/pull/8/files#diff-1da2c7edc898c70e5a79a9997c98cecc).
	echo "Installing composer dependences.\n";
	shell_exec( 'rm -rf vendor' );
	$originalDependences = array(
		'squizlabs/PHP_CodeSniffer:dev-master#506feeeb6753b9904a165366dd78077b2879adb7',
		'wp-coding-standards/wpcs:dev-feature/new-multi-line-comment-formatting-sniffs#ce4d719296ebbecd01f57bc3729833e89b39ad99',
		'dealerdirect/phpcodesniffer-composer-installer:0.4.3',
	);
	$output = shell_exec( 'composer require ' . implode( ' ', $originalDependences ) );
	if ( DEBUG_LEVEL ) {
		echo $output;
	}

	// Grab the original phpcs.xml.dist, which wasn't included in the firt auto-fixer commit.
	$output = shell_exec( 'git checkout 81a48c2924bb1c1cd8ae5752397766f133950e18 -- phpcs.xml.dist' );
	if ( DEBUG_LEVEL ) {
		echo $output;
	}

	// Run phpcbf on patch files.
	echo "Running code formatter on the patch files.\n";
	$output = shell_exec( "vendor/bin/phpcbf $fileParams" );
	if ( DEBUG_LEVEL ) {
		echo $output;
	}

	// Remove the PHPCS/composer files that we had to add.
	echo shell_exec( 'git rm -f phpcs.xml.dist' );
	$output = shell_exec( 'rm composer.json composer.lock' );
	if ( DEBUG_LEVEL ) {
		echo $output;
	}

	// Commit the changed files.
	echo "Committing formatted patch files.\n";
	$output = shell_exec( "git add $fileParams" );
	if ( DEBUG_LEVEL ) {
		echo $output;
	}
	$output = shell_exec( 'git commit -m "Apply phpcbf to patch files"' );
	if ( DEBUG_LEVEL ) {
		echo $output;
	}

	foreach ( $fixerCommits as $commit ) {
		// Merge up to the commit before the next fixer commit
		// For merge conflicts, assume ours (with the patch) is correct
		echo "Merging up to the commit before $commit\n";
		$output = shell_exec( "git merge --strategy-option ours $commit~1 -m 'Merge changes before fixer commit $commit'" );
		if ( DEBUG_LEVEL > 1 ) {
			echo $output;
		}

		$mergeCommit = trim( shell_exec( 'git rev-parse HEAD' ) );

		// Merge the next fixer commit. Assume that we're correct for merge conflicts.
		echo "Merging fixer commit $commit\n";
		$output = shell_exec( "git merge --strategy-option ours $commit -m 'Merge fixer commit git merge $commit'" );
		if ( DEBUG_LEVEL > 1 ) {
			echo $output;
		}

		// Upgrade composer dependences.
		echo "Upgrading composer dependences\n";
		shell_exec( 'rm -rf vendor' );
		$output = shell_exec( 'composer install' );
		if ( DEBUG_LEVEL ) {
			echo $output;
		}

		// Revert the patched files to the previous commit.
		echo "Reverting patch files to previous commit.\n";
		$output = shell_exec( "git checkout $mergeCommit -- $fileParams" );
		if ( DEBUG_LEVEL ) {
			echo $output;
		}

		// Run phpcbf on patch files.
		echo "Running code formatter on the patch files.\n";
		$output = shell_exec( "vendor/bin/phpcbf $fileParams" );
		if ( DEBUG_LEVEL ) {
			echo $output;
		}

		// Commit the changed files.
		echo "Committing formatted patch files.\n";
		$output = shell_exec( "git add $fileParams" );
		if ( DEBUG_LEVEL ) {
			echo $output;
		}
		$output = shell_exec( 'git commit -m "Apply phpcbf to patch files"' );
		if ( DEBUG_LEVEL ) {
			echo $output;
		}
	}

	// Merge the rest of master.
	echo "Merging the rest of master.\n";
	$output = shell_exec( 'git merge --strategy-option ours master -m "Merge the rest of master"' );
	if ( DEBUG_LEVEL > 1 ) {
		echo $output;
	}

	// Create patch.
	$output = shell_exec( "git diff master..$ticket > ../working/$ticket-automerged.diff" );
	if ( DEBUG_LEVEL ) {
		echo $output;
	}

	// Delete branch
	if ( ! DEBUG_LEVEL ) {
		shell_exec( 'git checkout master' );
		shell_exec( "git branch -D $ticket" );
	}

	if ( UPLOAD_PATCH ) {
		$diffContent = base64_encode( file_get_contents( "../working/$ticket-automerged.diff" ) );

		$attachmentRequest = "<?xml version=\"1.0\"? >
		<methodCall>
			<methodName>ticket.putAttachment</methodName>
			<params>
				<param><int>$ticket</int></param>
				<param><string>$ticket.diff</string></param>
				<param><string>Auto-refreshed patch</string></param>
				<param><base64>$diffContent</base64></param>
				<param><boolean>0</boolean></param>
			</params>
		</methodCall>";

		$conn = curl_init( 'https://core.trac.wordpress.org/login/xmlrpc' );

		if ( DEBUG_LEVEL ) {
			curl_setopt( $conn, CURLOPT_VERBOSE, 1 );
		}

		curl_setopt( $conn, CURLOPT_RETURNTRANSFER, 1 );
		curl_setopt( $conn, CURLOPT_USERAGENT, 'Patch Auto Refresher' );
		curl_setopt( $conn, CURLOPT_HTTPHEADER, array( 'Content-Type: application/xml; charset=utf-8' ) );
		curl_setopt( $conn, CURLOPT_POST, 1 );
		curl_setopt( $conn, CURLOPT_POSTFIELDS, $attachmentRequest );

		curl_setopt( $conn, CURLOPT_USERPWD, WPORG_USER . ':' . WPORG_PASSWORD );

		$response = curl_exec( $conn );
		if ( DEBUG_LEVEL ) {
			echo $response;
		}
	}
}
