<?php
namespace yiiunit\framework\base;

use yii\base\Behavior;
use yii\base\Component;
use yii\base\Event;
use yiiunit\TestCase;

function globalEventHandler($event)
{
	$event->sender->eventHandled = true;
}

function globalEventHandler2($event)
{
	$event->sender->eventHandled = true;
	$event->handled = true;
}

/**
 * @group base
 */
class ComponentTest extends TestCase
{
	/**
	 * @var NewComponent
	 */
	protected $component;

	protected function setUp()
	{
		parent::setUp();
		$this->mockApplication();
		$this->component = new NewComponent();
	}

	protected function tearDown()
	{
		parent::tearDown();
		$this->component = null;
	}

	public function testClone()
	{
		$component = new NewComponent();
		$behavior = new NewBehavior();
		$component->attachBehavior('a', $behavior);
		$this->assertSame($behavior, $component->getBehavior('a'));
		$component->on('test', 'fake');
		$this->assertTrue($component->hasEventHandlers('test'));

		$clone = clone $component;
		$this->assertNotSame($component, $clone);
		$this->assertNull($clone->getBehavior('a'));
		$this->assertFalse($clone->hasEventHandlers('test'));
	}
	
	public function testHasProperty()
	{
		$this->assertTrue($this->component->hasProperty('Text'));
		$this->assertTrue($this->component->hasProperty('text'));
		$this->assertFalse($this->component->hasProperty('Caption'));
		$this->assertTrue($this->component->hasProperty('content'));
		$this->assertFalse($this->component->hasProperty('content', false));
		$this->assertFalse($this->component->hasProperty('Content'));
	}

	public function testCanGetProperty()
	{
		$this->assertTrue($this->component->canGetProperty('Text'));
		$this->assertTrue($this->component->canGetProperty('text'));
		$this->assertFalse($this->component->canGetProperty('Caption'));
		$this->assertTrue($this->component->canGetProperty('content'));
		$this->assertFalse($this->component->canGetProperty('content', false));
		$this->assertFalse($this->component->canGetProperty('Content'));
	}

	public function testCanSetProperty()
	{
		$this->assertTrue($this->component->canSetProperty('Text'));
		$this->assertTrue($this->component->canSetProperty('text'));
		$this->assertFalse($this->component->canSetProperty('Object'));
		$this->assertFalse($this->component->canSetProperty('Caption'));
		$this->assertTrue($this->component->canSetProperty('content'));
		$this->assertFalse($this->component->canSetProperty('content', false));
		$this->assertFalse($this->component->canSetProperty('Content'));

		// behavior
		$this->assertFalse($this->component->canSetProperty('p2'));
		$behavior = new NewBehavior();
		$this->component->attachBehavior('a', $behavior);
		$this->assertTrue($this->component->canSetProperty('p2'));
		$this->component->detachBehavior('a');
	}

	public function testGetProperty()
	{
		$this->assertTrue('default' === $this->component->Text);
		$this->setExpectedException('yii\base\UnknownPropertyException');
		$value2 = $this->component->Caption;
	}

	public function testSetProperty()
	{
		$value = 'new value';
		$this->component->Text = $value;
		$this->assertEquals($value, $this->component->Text);
		$this->setExpectedException('yii\base\UnknownPropertyException');
		$this->component->NewMember = $value;
	}

	public function testIsset()
	{
		$this->assertTrue(isset($this->component->Text));
		$this->assertFalse(empty($this->component->Text));

		$this->component->Text = '';
		$this->assertTrue(isset($this->component->Text));
		$this->assertTrue(empty($this->component->Text));

		$this->component->Text = null;
		$this->assertFalse(isset($this->component->Text));
		$this->assertTrue(empty($this->component->Text));


		$this->assertFalse(isset($this->component->p2));
		$this->component->attachBehavior('a', new NewBehavior());
		$this->component->setP2('test');
		$this->assertTrue(isset($this->component->p2));
	}

	public function testCallUnknownMethod()
	{
		$this->setExpectedException('yii\base\UnknownMethodException');
		$this->component->unknownMethod();
	}

	public function testUnset()
	{
		unset($this->component->Text);
		$this->assertFalse(isset($this->component->Text));
		$this->assertTrue(empty($this->component->Text));

		$this->component->attachBehavior('a', new NewBehavior());
		$this->component->setP2('test');
		$this->assertEquals('test', $this->component->getP2());

		unset($this->component->p2);
		$this->assertNull($this->component->getP2());
	}

	public function testUnsetReadonly()
	{
		$this->setExpectedException('yii\base\InvalidCallException');
		unset($this->component->object);
	}

	public function testOn()
	{
		$this->assertFalse($this->component->hasEventHandlers('click'));
		$this->component->on('click', 'foo');
		$this->assertTrue($this->component->hasEventHandlers('click'));

		$this->assertFalse($this->component->hasEventHandlers('click2'));
		$p = 'on click2';
		$this->component->$p = 'foo2';
		$this->assertTrue($this->component->hasEventHandlers('click2'));
	}

	public function testOff()
	{
		$this->assertFalse($this->component->hasEventHandlers('click'));
		$this->component->on('click', 'foo');
		$this->assertTrue($this->component->hasEventHandlers('click'));
		$this->component->off('click', 'foo');
		$this->assertFalse($this->component->hasEventHandlers('click'));

		$this->component->on('click2', 'foo');
		$this->component->on('click2', 'foo2');
		$this->component->on('click2', 'foo3');
		$this->assertTrue($this->component->hasEventHandlers('click2'));
		$this->component->off('click2', 'foo3');
		$this->assertTrue($this->component->hasEventHandlers('click2'));
		$this->component->off('click2');
		$this->assertFalse($this->component->hasEventHandlers('click2'));
	}

	public function testTrigger()
	{
		$this->component->on('click', [$this->component, 'myEventHandler']);
		$this->assertFalse($this->component->eventHandled);
		$this->assertNull($this->component->event);
		$this->component->raiseEvent();
		$this->assertTrue($this->component->eventHandled);
		$this->assertEquals('click', $this->component->event->name);
		$this->assertEquals($this->component, $this->component->event->sender);
		$this->assertFalse($this->component->event->handled);

		$eventRaised = false;
		$this->component->on('click', function ($event) use (&$eventRaised) {
			$eventRaised = true;
		});
		$this->component->raiseEvent();
		$this->assertTrue($eventRaised);

		// raise event w/o parameters
		$eventRaised = false;
		$this->component->on('test', function ($event) use (&$eventRaised) {
			$eventRaised = true;
		});
		$this->component->trigger('test');
		$this->assertTrue($eventRaised);
	}

	public function testHasEventHandlers()
	{
		$this->assertFalse($this->component->hasEventHandlers('click'));
		$this->component->on('click', 'foo');
		$this->assertTrue($this->component->hasEventHandlers('click'));
	}

	public function testStopEvent()
	{
		$component = new NewComponent;
		$component->on('click', 'yiiunit\framework\base\globalEventHandler2');
		$component->on('click', [$this->component, 'myEventHandler']);
		$component->raiseEvent();
		$this->assertTrue($component->eventHandled);
		$this->assertFalse($this->component->eventHandled);
	}

	public function testAttachBehavior()
	{
		$component = new NewComponent;
		$this->assertFalse($component->hasProperty('p'));
		$this->assertFalse($component->behaviorCalled);
		$this->assertNull($component->getBehavior('a'));

		$behavior = new NewBehavior;
		$component->attachBehavior('a', $behavior);
		$this->assertSame($behavior, $component->getBehavior('a'));
		$this->assertTrue($component->hasProperty('p'));
		$component->test();
		$this->assertTrue($component->behaviorCalled);

		$this->assertSame($behavior, $component->detachBehavior('a'));
		$this->assertFalse($component->hasProperty('p'));
		$this->setExpectedException('yii\base\UnknownMethodException');
		$component->test();

		$p = 'as b';
		$component = new NewComponent;
		$component->$p = ['class' => 'NewBehavior'];
		$this->assertSame($behavior, $component->getBehavior('a'));
		$this->assertTrue($component->hasProperty('p'));
		$component->test();
		$this->assertTrue($component->behaviorCalled);
	}

	public function testAttachBehaviors()
	{
		$component = new NewComponent;
		$this->assertNull($component->getBehavior('a'));
		$this->assertNull($component->getBehavior('b'));

		$behavior = new NewBehavior;

		$component->attachBehaviors([
			'a' => $behavior,
			'b' => $behavior,
		]);

		$this->assertSame(['a' => $behavior, 'b' => $behavior], $component->getBehaviors());
	}

	public function testDetachBehavior()
	{
		$component = new NewComponent;
		$behavior = new NewBehavior;

		$component->attachBehavior('a', $behavior);
		$this->assertSame($behavior, $component->getBehavior('a'));

		$detachedBehavior = $component->detachBehavior('a');
		$this->assertSame($detachedBehavior, $behavior);
		$this->assertNull($component->getBehavior('a'));

		$detachedBehavior = $component->detachBehavior('z');
		$this->assertNull($detachedBehavior);
	}

	public function testDetachBehaviors()
	{
		$component = new NewComponent;
		$behavior = new NewBehavior;

		$component->attachBehavior('a', $behavior);
		$this->assertSame($behavior, $component->getBehavior('a'));
		$component->attachBehavior('b', $behavior);
		$this->assertSame($behavior, $component->getBehavior('b'));

		$component->detachBehaviors();
		$this->assertNull($component->getBehavior('a'));
		$this->assertNull($component->getBehavior('b'));
	}

	public function testSetReadOnlyProperty()
	{
		$this->setExpectedException(
			'\yii\base\InvalidCallException',
			'Setting read-only property: yiiunit\framework\base\NewComponent::object'
		);
		$this->component->object = 'z';
	}

	public function testSetPropertyOfBehavior()
	{
		$this->assertNull($this->component->getBehavior('a'));

		$behavior = new NewBehavior;
		$this->component->attachBehaviors([
			'a' => $behavior,
		]);
		$this->component->p = 'Yii is cool.';

		$this->assertSame('Yii is cool.', $this->component->getBehavior('a')->p);
	}

	public function testSettingBehaviorWithSetter()
	{
		$behaviorName = 'foo';
		$this->assertNull($this->component->getBehavior($behaviorName));
		$p = 'as ' . $behaviorName;
		$this->component->$p = __NAMESPACE__ .  '\NewBehavior';
		$this->assertSame(__NAMESPACE__ .  '\NewBehavior', get_class($this->component->getBehavior($behaviorName)));
	}

	public function testWriteOnlyProperty()
	{
		$this->setExpectedException(
			'\yii\base\InvalidCallException',
			'Getting write-only property: yiiunit\framework\base\NewComponent::writeOnly'
		);
		$this->component->writeOnly;
	}

	public function testSuccessfulMethodCheck()
	{
		$this->assertTrue($this->component->hasMethod('hasProperty'));
	}

	public function testTurningOffNonExistingBehavior()
	{
		$this->assertFalse($this->component->hasEventHandlers('foo'));
		$this->assertFalse($this->component->off('foo'));
	}
}

class NewComponent extends Component
{
	private $_object = null;
	private $_text = 'default';
	private $_items = [];
	public $content;

	public function getText()
	{
		return $this->_text;
	}

	public function setText($value)
	{
		$this->_text = $value;
	}

	public function getObject()
	{
		if (!$this->_object) {
			$this->_object = new self;
			$this->_object->_text = 'object text';
		}
		return $this->_object;
	}

	public function getExecute()
	{
		return function ($param) {
			return $param * 2;
		};
	}

	public function getItems()
	{
		return $this->_items;
	}

	public $eventHandled = false;
	public $event;
	public $behaviorCalled = false;

	public function myEventHandler($event)
	{
		$this->eventHandled = true;
		$this->event = $event;
	}

	public function raiseEvent()
	{
		$this->trigger('click', new Event);
	}

	public function setWriteOnly()
	{
	}
}

class NewBehavior extends Behavior
{
	public $p;
	private $p2;

	public function getP2()
	{
		return $this->p2;
	}

	public function setP2($value)
	{
		$this->p2 = $value;
	}

	public function test()
	{
		$this->owner->behaviorCalled = true;
		return 2;
	}
}

class NewComponent2 extends Component
{
	public $a;
	public $b;
	public $c;

	public function __construct($b, $c)
	{
		$this->b = $b;
		$this->c = $c;
	}
}
