<?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;
    }
}
