<?php

use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestResult;
use PHPUnit\Framework\AssertionFailedError;

require_once(__DIR__ . '/../lib/RssBridge.php');

Bridge::setDir(PATH_LIB_BRIDGES);

/**
 * This class checks bridges for implementation details:
 *
 * - A bridge must not implement public functions other than the ones specified
 *   by the bridge interfaces. Custom functions must be defined in private or
 *   protected scope.
 * - getName() must return a valid string (non-empty)
 * - getURI() must return a valid URI
 * - A bridge must define constants for NAME, URI, DESCRIPTION and MAINTAINER,
 *   CACHE_TIMEOUT and PARAMETERS are optional
 */
final class BridgeImplementationTest extends TestCase {

	private function CheckBridgePublicFunctions($bridgeName){

		$parent_methods = array();

		if(in_array('BridgeInterface', class_parents($bridgeName))) {
			$parent_methods = array_merge($parent_methods, get_class_methods('BridgeInterface'));
		}

		if(in_array('BridgeAbstract', class_parents($bridgeName))) {
			$parent_methods = array_merge($parent_methods, get_class_methods('BridgeAbstract'));
		}

		if(in_array('FeedExpander', class_parents($bridgeName))) {
			$parent_methods = array_merge($parent_methods, get_class_methods('FeedExpander'));
		}

		// Receive all non abstract methods
		$methods = array_diff(get_class_methods($bridgeName), $parent_methods);
		$method_names = implode(', ', $methods);

		$errmsg = $bridgeName
		. ' implements additional public method(s): '
		. $method_names
		. '! Custom functions must be defined in private or protected scope!';

		$this->assertEmpty($method_names, $errmsg);

	}

	private function CheckBridgeGetNameDefaultValue($bridgeName){

		if(in_array('BridgeAbstract', class_parents($bridgeName))) { // Is bridge

			if(!$this->isFunctionMemberOf($bridgeName, 'getName'))
				return;

			$bridge = new $bridgeName();
			$abstract = new BridgeAbstractTest();

			$message = $bridgeName . ': \'getName\' must return a valid name!';

			$this->assertNotEmpty(trim($bridge->getName()), $message);

		}

	}

	// Checks whether the getURI function returns empty or default values
	private function CheckBridgeGetURIDefaultValue($bridgeName){

		if(in_array('BridgeAbstract', class_parents($bridgeName))) { // Is bridge

			if(!$this->isFunctionMemberOf($bridgeName, 'getURI'))
				return;

			$bridge = new $bridgeName();
			$abstract = new BridgeAbstractTest();

			$message = $bridgeName . ': \'getURI\' must return a valid URI!';

			$this->assertNotEmpty(trim($bridge->getURI()), $message);

		}

	}

	private function CheckBridgePublicConstants($bridgeName){

		// Assertion only works for BridgeAbstract!
		if(in_array('BridgeAbstract', class_parents($bridgeName))) {

			$ref = new ReflectionClass($bridgeName);
			$constants = $ref->getConstants();

			$ref = new ReflectionClass('BridgeAbstract');
			$parent_constants = $ref->getConstants();

			foreach($parent_constants as $key => $value) {

				$this->assertArrayHasKey($key, $constants, 'Constant ' . $key . ' missing in ' . $bridgeName);

				// Skip optional constants
				if($key !== 'PARAMETERS' && $key !== 'CACHE_TIMEOUT') {
					$this->assertNotEquals($value, $constants[$key], 'Constant ' . $key . ' missing in ' . $bridgeName);
				}

			}

		}

	}

	private function isFunctionMemberOf($bridgeName, $functionName){

		$bridgeReflector = new ReflectionClass($bridgeName);
		$bridgeMethods = $bridgeReflector->GetMethods();
		$bridgeHasMethod = false;

		foreach($bridgeMethods as $method) {

			if($method->name === $functionName && $method->class === $bridgeReflector->name) {
				return true;
			}

		}

		return false;

	}

	public function testBridgeImplementation($bridgeName){

		require_once('bridges/' . $bridgeName . '.php');

		$this->CheckBridgePublicFunctions($bridgeName);
		$this->CheckBridgePublicConstants($bridgeName);
		$this->CheckBridgeGetNameDefaultValue($bridgeName);
		$this->CheckBridgeGetURIDefaultValue($bridgeName);

	}

	public function count() {
		return count(Bridge::listBridges());
	}

	public function run(TestResult $result = null) {

		if ($result === null) {
			$result = new TestResult;
		}

		foreach (Bridge::listBridges() as $bridge) {

			$bridge .= 'Bridge';

			$result->startTest($this);
			PHP_Timer::start();
			$stopTime = null;

			try {
				$this->testBridgeImplementation($bridge);
			} catch (AssertionFailedError $e) {

				$stopTime = PHP_Timer::stop();
				$result->addFailure($this, $e, $stopTime);

			} catch (Exception $e) {

				$stopTime = PHP_Timer::stop();
				$result->addError($this, $e, $stopTime);

			}

			if ($stopTime === null) {
				$stopTime = PHP_Timer::stop();
			}

			$result->endTest($this, $stopTime);

		}

		return $result;
	}
}

class BridgeAbstractTest extends BridgeAbstract {
	public function collectData(){}
}