<?php
/**
 * core.framework
 *
 * @category   Core
 * @package    Core_Asset
 * @subpackage UnitTests
 * @copyright  Copyright (c) 2013. Burza d.o.o. (http://web.burza.hr/en/)
 * @license    proprietary
 */

// sadly, vfsStream does not follow PSR-0, burn in flames
require_once 'vfsStream/vfsStream.php';

/**
 * @category   Core
 * @package    Core_Asset
 * @subpackage UnitTests
 * @copyright  Copyright (c) 2013. Burza d.o.o. (http://web.burza.hr/en/)
 * @license    proprietary
 * @group      Core_Asset
 */
class Core_Asset_ManagerTest extends PHPUnit_Framework_TestCase
{
    /**
     * @var Core_Asset_Manager
     */
    protected $_object;

    /**
     * vfsStream mock for filesystem
     *
     * @var string
     */
    protected $_root        = 'vfs://root';

    /**
     * Example CSS, we mock file with actual data quite a lot (DRY)
     */
    protected $_cssCode     = null;

    protected function setUp()
    {
        // using vfsStream for mocking out the filesystem!
        if (class_exists('vfsStream', false) === false) {
            $this->markTestSkipped('Core_AssetTest requires vfsStream, see http://vfs.bovigo.org/.');
        }

        $this->_cssCode = "
/** STRUCTURE **/
html        { overflow-y: scroll; }

/** GRID **/
/* container */
.container_full  { width: 100%; text-align: center; display: inline-block; position: relative; }
.container       { width: 984px; text-align: left; margin: 0 auto; zoom: 1; position: relative; display: inline-block; }
        ";

        vfsStream::setup('root');

        // create directory structure
        $this->_createDirectories();

        // set APPLICATION_ROOT and DOCUMENT_ROOT manually
        Core_Application::getInstance()->setRoot($this->_root);
        Core_Application::getInstance()->setDocumentRoot($this->_root . '/app/public');

        $this->_object = new Core_Asset_Manager;
    }

    /**
     * @covers Core_Asset_Manager::addFilter
     * @covers Core_Asset_Manager::getFilters
     */
    public function testCanAddAndGetFilters()
    {
        $filter1 = $this->getMock('Core_Asset_Filter_Interface');
        $filter2 = $this->getMock('Core_Asset_Filter_Interface');

        $this->_object
            ->addFilter($filter1)
            ->addFilter($filter2);

        $expected = array($filter1, $filter2);

        $this->assertEquals($expected, $this->_object->getFilters());
    }

    /**
     * @covers Core_Asset_Manager::setProcessingEnabled
     * @covers Core_Asset_Manager::getProcessingEnabled
     */
    public function testCanSetAndGetProcessing()
    {
        $this->_object->setProcessingEnabled(true);
        $this->assertEquals(true, $this->_object->getProcessingEnabled());
    }

    /**
     * @expectedException InvalidArgumentException
     *
     * @covers Core_Asset_Manager::getProcessingEnabled
     */
    public function testGetProcessingEnabledThrowsInvalidArgumentExceptionIfNotPreviouslySet()
    {
        $this->_object->getProcessingEnabled();
    }

    /**
     * @expectedException LogicException
     *
     * @covers Core_Asset_Manager::dump
     */
    public function testIfDumpThrowsLogicExceptionWhenNoAssetsWereAdded()
    {
        $this->_object->dump(array());
    }

    /**
     * @covers Core_Asset_Manager::dump
     */
    public function testDumpReturnsValidData()
    {
        $storage    = $this->getMock('Core_Storage_Strategy_Hash', array('getUrlRoot'), array());
        $storage
            ->expects($this->any())
            ->method('getUrlRoot')
            ->will($this->returnValue($this->_root . '/app/public'));

        $this->_object->setStorage($storage);

        // mock files being used in this test, so they exist
        $file1 = $this->_root . '/app/public/css/min.css';
        $file2 = $this->_root . '/app/public/css/default.css';

        // fill them with data
        file_put_contents($file1 ,$this->_cssCode);
        file_put_contents($file2, $this->_cssCode);

        // get mock asset objects and create array
        $asset1 = $this->getMock('Core_Asset', array('getPath'), array($file1));
        $asset1
            ->expects($this->any())
            ->method('getPath')
            ->will($this->returnValue($file1));

        $asset2 = $this->getMock('Core_Asset', array('getPath'), array($file2));
        $asset2
            ->expects($this->any())
            ->method('getPath')
            ->will($this->returnValue($file2));
        $assets     = array($asset1, $asset2);
        $expected   = trim(file_get_contents($file1)) . PHP_EOL . trim(file_get_contents($file2)). PHP_EOL;
        $this->assertEquals($expected, $this->_object->dump($assets));
    }

     /**
     * @covers Core_Asset_Manager::dump
     */
    public function testDumpReturnsValidDataWhenUsingExternalAsset()
    {
        $storage    = $this->getMock('Core_Storage_Strategy_Hash', array('getUrlRoot'), array());
        $storage
            ->expects($this->any())
            ->method('getUrlRoot')
            ->will($this->returnValue($this->_root . '/app/public'));

        $this->_object->setStorage($storage);

        // mock files being used in this test, so they exist - since we have external assets, mocked asset2 will return
        // proper path to the file (non-aliased one!)
        $file1 = $this->_root . '/app/public/css/min.css';
        $file2 = $this->_root . '/framework/asset/styles/global.css';

        file_put_contents($file1 ,$this->_cssCode);
        file_put_contents($file2, $this->_cssCode);

        // get mock asset objects and create array
        $asset1 = $this->getMock('Core_Asset', array('getPath'), array($file1));
        $asset1
            ->expects($this->any())
            ->method('getPath')
            ->will($this->returnValue($file1));

        $asset2 = $this->getMock('Core_Asset', array('getPath'), array($file2));
        $asset2
            ->expects($this->any())
            ->method('getPath')
            ->will($this->returnValue($file2));

        $assets     = array($asset1, $asset2);
        $expected   = trim(file_get_contents($file1)). PHP_EOL . trim(file_get_contents($file2)). PHP_EOL;

        $this->assertEquals($expected, $this->_object->dump($assets));
    }



    /**
     * @covers Core_Asset_Manager::dump
     * @covers Core_Asset_Manager::_getType
     */
    public function testDumpWithFilterReturnsValidData()
    {
        $storage    = $this->getMock('Core_Storage_Strategy_Hash', array('getUrlRoot'), array());
        $storage
            ->expects($this->any())
            ->method('getUrlRoot')
            ->will($this->returnValue($this->_root . '/app/public'));

        $this->_object->setStorage($storage);

        // mock files being used in this test, so they exist
        $file1  = $this->_root . '/app/public/css/min.css';
        $file2  = $this->_root . '/app/public/css/default.css';

        file_put_contents($file1, $this->_cssCode);
        file_put_contents($file2, $this->_cssCode);

        $filter = new Core_Asset_Filter_Test();

        $this->_object->addFilter($filter);

        $asset1 = $this->getMock('Core_Asset', array('getPath'), array($file1));
        $asset1
            ->expects($this->any())
            ->method('getPath')
            ->will($this->returnValue($file1));

        $asset2 = $this->getMock('Core_Asset', array('getPath'), array($file2));
        $asset2
            ->expects($this->any())
            ->method('getPath')
            ->will($this->returnValue($file2));

        $assets = array($asset1, $asset2);

        $expected = trim(file_get_contents($file1)). PHP_EOL . trim(file_get_contents($file2)). PHP_EOL;

        $this->assertEquals($expected, $this->_object->dump($assets));
    }

    /**
     * @covers Core_Asset_Manager::setStorage
     * @covers Core_Asset_Manager::getStorage
     */
    public function testCanSetAndGetStorage()
    {
        $storageMock = $this->getMock('Core_Storage_Strategy_Hash', array(), array());
        $this->_object->setStorage($storageMock);
        $this->assertEquals($storageMock, $this->_object->getStorage());
    }

    /**
     * @covers Core_Asset_Manager::setAlias
     * @covers Core_Asset_Manager::setAliases
     */
    public function testCanSetAndGetMultipleAliases()
    {
         $aliases = array(
            '/framework/javascript/'    => '/../../framework/assets/javascript/lib/',
            '/framework/styles/'        => '/../../framework/assets/style/lib/',
        );
        $this->_object->setAliases($aliases);

        $this->assertEquals($aliases, $this->_object->getAliases());
    }

    /**
     * @covers Core_Asset_Manager::setAlias
     * @covers Core_Asset_Manager::getAliases
     */
    public function testCanSetAndGetAliases()
    {
        $aliases = array(
            '/framework/javascript/'    => '/../../framework/assets/javascript/lib/',
            '/framework/styles/'        => '/../../framework/assets/style/lib/',
        );

        $this->_object->setAlias('/framework/javascript/',  '/../../framework/assets/javascript/lib/');
        $this->_object->setAlias('/framework/styles/',      '/../../framework/assets/style/lib/');

        $this->assertEquals($aliases, $this->_object->getAliases());
    }

    /**
     * @covers Core_Asset_Manager::setAlias
     *
     * @expectedException InvalidArgumentException
     */
    public function testSetAliasWithEmptyAliasKeyThrowsInvalidArgumentException()
    {
        $this->_object->setAlias('', '/../../framework/asset');
    }

    /**
     * @expectedException LogicException
     *
     * @covers Core_Asset_Manager::getStorage
     */
    public function testGetStorageThrowsLogicWhenStorageNotSet()
    {
        $this->_object->getStorage();
    }

    /**
     * @expectedException InvalidArgumentException
     */
    public function testGetProcessingEnabledThrowsInvalidArgumentExceptionWhenNoProcessingFlagIsSet()
    {
        $this->_object->getProcessingEnabled();
    }

    /**
     * @covers Core_Asset_Manager::process
     * @expectedException InvalidArgumentException
     */
    public function testProcessInvalidDataThrowsInvalidArgumentException()
    {
        $this->_object->process(true);
    }

    /**
     * @covers Core_Asset_Manager::process
     * @covers Core_Asset_Manager::_isAlias
     *
     * @expectedException InvalidArgumentException
     */
    public function testProcessThrowsInvalidArgumentExceptionForInvalidAssets()
    {
        $storage = $this->getMock('Core_Storage_Strategy_Hash', array('getRoot', 'getUrlRoot', 'getDomain'), array());
        $storage
            ->expects($this->any())
            ->method('getRoot')
            ->will($this->returnValue($this->_root));
        $storage
            ->expects($this->any())
            ->method('getUrlRoot')
            ->will($this->returnValue($this->_root . '/app/public'));
        $storage
            ->expects($this->any())
            ->method('getDomain')
            ->will($this->returnValue('http://www.coolinarika.com'));

        $this->_object->setAlias('', '/../../framework/asset/styles/');
        $this->_object->setProcessingEnabled(false);
        $this->_object->setStorage($storage);

        $options        = array('enabled' => false);
        $tempAssets     = array(
            '',
            ' ',
        );
        $assets = $this->_object->process($tempAssets, $options);
    }

    /**
     * @covers Core_Asset_Manager::process
     * @covers Core_Asset_Manager::_isAlias
     */
    public function testProcessReturnsValidAssetWithProcessingDisabledAndUsingExternalAsset()
    {
        $file = $this->_root . '/framework/asset/styles/global.css';
        file_put_contents($file, $this->_cssCode);

        $storage = $this->getMock('Core_Storage_Strategy_Hash', array('getRoot', 'getUrlRoot', 'getDomain'), array());
        $storage
            ->expects($this->any())
            ->method('getRoot')
            ->will($this->returnValue($this->_root));
        $storage
            ->expects($this->any())
            ->method('getUrlRoot')
            ->will($this->returnValue($this->_root . '/app/public'));
        $storage
            ->expects($this->any())
            ->method('getDomain')
            ->will($this->returnValue('http://www.coolinarika.com'));

        $coreAsset = new Core_Asset($file, null, null, null, '/framework/styles/global.css');

        // so assets have a name set up, this way objects will be the same
        $coreAsset->getName();
        $coreAsset->getExtension();
        $coreAsset->setStorage($storage);

        $this->_object->setAlias('/framework/styles/', '/../../framework/asset/styles/');
        $this->_object->setProcessingEnabled(false);
        $this->_object->setStorage($storage);

        $options        = array('enabled' => false);
        $tempAssets     = array(
            '/framework/styles/global.css',
        );
        $assets = $this->_object->process($tempAssets, $options);

        foreach ($assets as $asset) {
            $listOfAssets[] = $asset->getUrlPath();
        }

        $this->assertEquals($listOfAssets, $tempAssets);
    }

    /**
     * @covers Core_Asset_Manager::process
     * @covers Core_Asset_Manager::_isAlias
     */
    public function testProcessReturnsValidMultipleAssetsWithProcessingDisabledAndUsingExternalAsset()
    {
        // mock files being used in this test, so they exist
        $file1  = $this->_root . '/app/public/css/min.css';
        $file2  = $this->_root . '/app/public/css/default.css';
        $file3  = $this->_root . '/framework/asset/styles/external.css';

        file_put_contents($file1, $this->_cssCode);
        file_put_contents($file2, $this->_cssCode);
        file_put_contents($file3, $this->_cssCode);

        $storage    = $this->getMock('Core_Storage_Strategy_Hash', array('getUrlRoot'), array());
        $storage
            ->expects($this->any())
            ->method('getUrlRoot')
            ->will($this->returnValue($this->_root . '/app/public'));

        $coreAsset1     = new Core_Asset($file1, null, null, null, '/css/min.css');
        $coreAsset2     = new Core_Asset($file2, null, null, null, '/css/default.css');
        $coreAsset3     = new Core_Asset($file3, null, null, null, '/framework/styles/external.css');

        // so assets have a name set up, this way objects will be the same
        $coreAsset1->getName();
        $coreAsset1->getExtension();
        $coreAsset2->getName();
        $coreAsset2->getExtension();
        $coreAsset3->getName();
        $coreAsset3->getExtension();

        $coreAsset1->setStorage($storage);
        $coreAsset2->setStorage($storage);
        $coreAsset3->setStorage($storage);

        $this->_object->setAlias('/framework/styles/', '/../../framework/asset/styles/');
        $this->_object->setProcessingEnabled(false);
        $this->_object->setStorage($storage);

        $options        = array('enabled' => false);
        $inputAssets    = array(
            '/css/min.css',
            '/css/default.css',
            '/framework/styles/external.css',         // maps into '/../../framework/asset/styles/external.css'
        );

        $outputAssets   = array(
            $coreAsset1->getUrlPath(),
            $coreAsset2->getUrlPath(),
            $coreAsset3->getUrlPath(),
        );

        foreach ($this->_object->process($inputAssets, $options) as $tempAsset) {
            $processedOutputAssets[] = $tempAsset->getUrlPath();
        }

        $this->assertEquals($outputAssets, $processedOutputAssets);
    }

    /**
     * @covers Core_Asset_Manager::process
     * @covers Core_Asset_Manager::_isAlias
     * @covers Core_Asset_Manager::_checkAssets
     * @covers Core_Asset_Manager::_getType
     *
     * @expectedException InvalidArgumentException
     */
    public function testProcessThrowsInvalidArgumentExceptionWhenAssetsAreNotOfTheSameType()
    {
        $this->markTestIncomplete('TODO');

        // mock files being used in this test, so they exist
        $file1  = $this->_root . '/app/public/css/min.css';
        $file2  = $this->_root . '/app/public/css/default.XXX';
        $file3  = $this->_root . '/framework/asset/styles/external.css';

        file_put_contents($file1, $this->_cssCode);
        file_put_contents($file2, $this->_cssCode);
        file_put_contents($file3, $this->_cssCode);

        $storage    = $this->getMock('Core_Storage_Strategy_Hash', array('getUrlRoot'), array());
        $storage
            ->expects($this->any())
            ->method('getUrlRoot')
            ->will($this->returnValue($this->_root . '/app/public'));

        $coreAsset1     = new Core_Asset($file1, null, null, null, '/css/min.css');
        $coreAsset2     = new Core_Asset($file2, null, null, null, '/css/default.XXX');
        $coreAsset3     = new Core_Asset($file3, null, null, null, '/framework/styles/external.css');

        // so assets have a name set up, this way objects will be the same
        $coreAsset1->getName();
        $coreAsset1->getExtension();
        $coreAsset2->getName();
        $coreAsset2->getExtension();
        $coreAsset3->getName();
        $coreAsset3->getExtension();

        $coreAsset1->setStorage($storage);
        $coreAsset2->setStorage($storage);
        $coreAsset3->setStorage($storage);

        $this->_object->setAlias('/framework/styles/', '/../../framework/asset/styles/');
        $this->_object->setProcessingEnabled(false);
        $this->_object->setStorage($storage);

        $options        = array('enabled' => false);
        $inputAssets    = array(
            '/css/min.css',
            '/css/default.XXX',
            '/framework/styles/external.css',         // maps into '/../../framework/asset/styles/external.css'
        );

        $this->_object->process($inputAssets, $options);

    }

    /**
     * @covers Core_Asset_Manager::process
     * @covers Core_Asset_Manager::setProcessingEnabled
     * @covers Core_Asset_Manager::getProcessingEnabled
     * @covers Core_Asset_Manager::_isAlias
     * @covers Core_Asset_Manager::_checkAssets
     */
    public function testProcessReturnsValidAssetsWithProcessingDisabled()
    {
        // mock files being used in this test, so they exist
        $file1  = $this->_root . '/app/public/css/abcdefgh.css';
        $file2  = $this->_root . '/app/public/css/default.css';

        file_put_contents($file1, $this->_cssCode);
        file_put_contents($file2, $this->_cssCode);

        $storage    = $this->getMock('Core_Storage_Strategy_Hash', array('getUrlRoot'), array());
        $storage
            ->expects($this->any())
            ->method('getUrlRoot')
            ->will($this->returnValue($this->_root . '/app/public'));

        $coreAsset1     = new Core_Asset($file1, null, null, null, '/css/abcdefgh.css');
        $coreAsset2     = new Core_Asset($file2, null, null, null, '/css/default.css');

        // so assets have a name set up, this way objects will be the same
        $coreAsset1->getName();
        $coreAsset1->getExtension();
        $coreAsset2->getName();
        $coreAsset2->getExtension();

        $coreAsset1->setStorage($storage);
        $coreAsset2->setStorage($storage);

        $this->_object->setAlias('/framework/styles/', '/../../framework/asset/styles/');
        $this->_object->setProcessingEnabled(false);
        $this->_object->setStorage($storage);

        $options        = array('enabled' => false);
        $inputAssets    = array(
            '/css/abcdefgh.css',
            '/css/default.css',
        );

        $outputAssets   = array(
            $coreAsset1->getUrlPath(),
            $coreAsset2->getUrlPath(),
        );

        foreach ($this->_object->process($inputAssets, $options) as $tempAsset) {
            $processedOutputAssets[] = $tempAsset->getUrlPath();
        }

        $this->assertEquals($outputAssets, $processedOutputAssets);
    }

    /**
     * @covers Core_Asset_Manager::process
     * @covers Core_Asset_Manager::_generateOutputFilename
     * @covers Core_Asset_Manager::_checkAssets
     */
    public function testProcessReturnsValidAssetWithProcessingEnabled()
    {
        $this->markTestIncomplete('TODO');

        // mock files being used in this test, so they exist
        $file1  = $this->_root . '/app/public/css/min.css';
        $file2  = $this->_root . '/app/public/css/default.css';

        file_put_contents($file1, $this->_cssCode);
        file_put_contents($file2, $this->_cssCode);

        $storage = $this->getMock('Core_Storage_Strategy_Hash', array('getUrlRoot', 'getRoot', 'getDomain'), array());
        $storage
            ->expects($this->any())
            ->method('getUrlRoot')
            ->will($this->returnValue($this->_root . '/app/public'));
        $storage
            ->expects($this->any())
            ->method('getRoot')
            ->will($this->returnValue($this->_root));
        $storage
            ->expects($this->any())
            ->method('getDomain')
            ->will($this->returnValue(''));

        $this->_object->setProcessingEnabled(true);
        $this->_object->setStorage($storage);

        $expectedAssetUrl = '/assets/69f2bb2299887134c952c4db579755f6.css?v=6367be619b266be41092573dcd89d96c';

        $options    = array('enabled' => true);
        $tempAssets = array('/css/min.css', '/css/default.css');
        $result     = $this->_object->process($tempAssets, $options);

        $this->assertEquals($expectedAssetUrl, $result[0]->getUrlPath());
    }

    /**
     * @covers Core_Asset_Manager::process
     * @covers Core_Asset_Manager::_mapAliasedAsset
     * @covers Core_Asset_Manager::_checkAssets
     */
    public function testProcessReturnsValidAssetWithProcessingEnabledAndUsingExternalAsset()
    {
        $this->markTestIncomplete('TODO');

        // mock files being used in this test, so they exist
        $file1  = $this->_root . '/app/public/css/min.css';
        $file2  = $this->_root . '/app/public/css/default.css';
        $file3  = $this->_root . '/framework/asset/styles/external.css';

        file_put_contents($file1, $this->_cssCode);
        file_put_contents($file2, $this->_cssCode);
        file_put_contents($file3, $this->_cssCode);

        $storage = $this->getMock('Core_Storage_Strategy_Hash', array('getUrlRoot', 'getRoot', 'getDomain'), array());
        $storage
            ->expects($this->any())
            ->method('getUrlRoot')
            ->will($this->returnValue($this->_root . '/app/public'));
        $storage
            ->expects($this->any())
            ->method('getRoot')
            ->will($this->returnValue($this->_root));
        $storage
            ->expects($this->any())
            ->method('getDomain')
            ->will($this->returnValue(''));

        $this->_object->setProcessingEnabled(true);
        $this->_object->setAlias('/framework/styles/', '/../../framework/asset/styles/');
        $this->_object->setStorage($storage);

        $options    = array('enabled' => true);
        $tempAssets = array(
            '/css/min.css',
            '/css/default.css',
            '/framework/styles/external.css',
        );

        $dumpData       = $this->_cssCode . $this->_cssCode . $this->_cssCode;
        $filenameData   = 'min.css-default.css-external.css';
        $expectedUrl    = '/assets/' . md5($filenameData) .'.css?v='. md5($dumpData);

        // we get array of results, this time processed so only 1 entry
        $result     = $this->_object->process($tempAssets, $options);

        $this->assertEquals($expectedUrl, $result[0]->getUrlPath());
    }

    /**
     * Create directories required to properly test manager
     */
    protected function _createDirectories()
    {
        mkdir($this->_root . '/app',                        0777);
        mkdir($this->_root . '/app/public',                 0777);
        mkdir($this->_root . '/app/public/css',             0777);
        mkdir($this->_root . '/app/public/assets',          0777);
        mkdir($this->_root . '/app/public/asset',           0777);
        mkdir($this->_root . '/app/public/framework',       0777);
        mkdir($this->_root . '/app/public/framework/styles',0777);
        mkdir($this->_root . '/framework/',                 0777);
        mkdir($this->_root . '/framework/asset',            0777);
        mkdir($this->_root . '/framework/asset/styles',     0777);
        mkdir($this->_root . '/framework/asset/styles/css', 0777);
        mkdir($this->_root . '/framework/asset/javascript', 0777);
    }

}
