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

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

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

    protected function setUp()
    {
        vfsStream::setup($this->_vfsPath());
        $storage     = $this->_vfsPath('/storage');
        mkdir($storage);
        $this->_object = new Core_Storage_Strategy_Hash;
        $this->_object->setRoot($storage);
        $this->_object->setDomain('https://cdn.net.hr');
    }

    /**
     * @covers Core_Storage_Strategy_Hash::setDepth
     * @covers Core_Storage_Strategy_Hash::getDepth
     * @covers Core_Storage_Strategy_Hash::<protected>
     */
    public function testCanSetAndGetDepth()
    {
        $depth = 4;
        $this->_object->setDepth($depth);
        $this->assertEquals($depth, $this->_object->getDepth());
    }

    /**
     * @covers Core_Storage_Strategy_Hash::store
     * @covers Core_Storage_Strategy_Hash::<protected>
     */
    public function testCanStoreFile()
    {
        $tmp    = $this->_vfsFile();
        $file   = $this->_object->store($tmp);
        $target = $this->_vfsPath('/storage/'. basename($tmp));

        $this->assertEquals($target, $file->getPath());
    }

    /**
     * @covers Core_Storage_Strategy_Hash::store
     * @covers Core_Storage_Strategy_Hash::<protected>
     */
    public function testCanStoreFileWithDepth()
    {
        $this->_object->setDepth(2);
        $tmp    = $this->_vfsFile();
        $tmpBase= basename($tmp);
        $file   = $this->_object->store($tmp);
        $target = $this->_vfsPath('/storage/'. $tmpBase[0].'/'. $tmpBase[1]. '/'. basename($tmp));

        $this->assertEquals($target, $file->getPath());
    }

    /**
     * @covers Core_Storage_Strategy_Hash::store
     * @covers Core_Storage_Strategy_Hash::<protected>
     *
     * @large
     */
    public function testCanStoreFileWithLargeDepth()
    {
        $this->_object->setDepth(8);
        $tmp    = $this->_vfsFile();
        $tmpBase= basename($tmp);
        $file   = $this->_object->store($tmp);
        $target = $this->_vfsPath(
            '/storage/'.
            $tmpBase[0].'/'.
            $tmpBase[1].'/'.
            $tmpBase[2].'/'.
            $tmpBase[3].'/'.
            $tmpBase[4].'/'.
            $tmpBase[5].'/'.
            $tmpBase[6].'/'.
            $tmpBase[7].'/'.
            basename($tmp)
        );

        $this->assertEquals($target, $file->getPath());
    }

    /**
     * @covers Core_Storage_Strategy_Hash::store
     * @covers Core_Storage_Strategy_Hash::<protected>
     */
    public function testStoringANonExistantFileThrowsAnRuntimeException()
    {
        $this->setExpectedException('RuntimeException');

        $this->_object->store('no-such-file.txt');
    }

    /**
     * @covers Core_Storage_Strategy_Hash::store
     * @covers Core_Storage_Strategy_Hash::<protected>
     */
    public function testStoringANonReadableFileThrowsAnRuntimeException()
    {
        $tmp     = $this->_vfsFile();
        $this->_vfsChmod($tmp, 0000);
        $this->setExpectedException('RuntimeException');

        $this->_object->store($tmp);
    }

    /**
     * @covers Core_Storage_Strategy_Hash::store
     * @covers Core_Storage_Strategy_Hash::<protected>
     */
    public function testStoringToANonExistantStorageCreatesThatStorageIfParentDirIsWriteable()
    {
        $tmp     = $this->_vfsFile();
        $storage = $this->_vfsPath('/doesnt-exist');
        $this->_object->setRoot($storage);
        $file    = $this->_object->store($tmp);

        $this->assertTrue(file_exists($file->getPath()));
    }

    /**
     * @covers Core_Storage_Strategy_Hash::store
     * @covers Core_Storage_Strategy_Hash::<protected>
     */
    public function testStoringToANonExistantStorageWithDepthCreatesThatStorageIfParentDirIsWriteable()
    {
        $tmp     = $this->_vfsFile();
        $storage = $this->_vfsPath('/doesnt-exist');
        $this->_object->setRoot($storage);
        $this->_object->setDepth(1);
        $file    = $this->_object->store($tmp);

        $this->assertTrue(file_exists($file->getPath()));
    }

    /**
     * @covers Core_Storage_Strategy_Hash::store
     * @covers Core_Storage_Strategy_Hash::<protected>
     */
    public function testStoringToANonExistantStorageThrowsARuntimeExceptionIfParentDirIsNotWriteable()
    {
        $tmp     = $this->_vfsFile();
        $storage = $this->_vfsPath('/doesnt-exist');
        $this->_vfsChmod(dirname($storage), 0000);
        $this->_object->setRoot($storage);
        $this->setExpectedException('RuntimeException');

        $this->_object->store($tmp);
    }

    /**
     * @covers Core_Storage_Strategy_Hash::store
     * @covers Core_Storage_Strategy_Hash::<protected>
     */
    public function testStoringToANonWritableStorageThrowsARuntimeException()
    {
        $tmp     = $this->_vfsFile();
        $storage = $this->_vfsPath('/ro-storage');
        mkdir($storage, 0000);
        $this->_object->setRoot($storage);
        $this->setExpectedException('RuntimeException');

        $this->_object->store($tmp);
    }

    /**
     * @covers Core_Storage_Strategy_Hash::has
     * @covers Core_Storage_Strategy_Hash::<protected>
     */
    public function testCanTestForFileExistance()
    {
        $tmp  = $this->_vfsFile();
        $file = $this->_object->store($tmp);

        $this->assertTrue($this->_object->has($file));
        $this->assertFalse($this->_object->has('no-such-file.txt'));
    }

    /**
     * @covers Core_Storage_Strategy_Hash::fetch
     * @covers Core_Storage_Strategy_Hash::<protected>
     */
    public function testCanFetchExistingFileViaInstance()
    {
        $tmp   = $this->_vfsFile();
        $file1 = $this->_object->store($tmp);
        $file2 = $this->_object->fetch($file1);

        $this->assertEquals($file1->getPath(), $file2->getPath());
    }

    /**
     * @covers Core_Storage_Strategy_Hash::fetch
     * @covers Core_Storage_Strategy_Hash::<protected>
     */
    public function testCanFetchExistingFileViaAbsolutePath()
    {
        $tmp   = $this->_vfsFile();
        $file1 = $this->_object->store($tmp);
        $file2 = $this->_object->fetch($file1->getPath());

        $this->assertEquals($file1->getPath(), $file2->getPath());
    }

    /**
     * @covers Core_Storage_Strategy_Hash::fetch
     * @covers Core_Storage_Strategy_Hash::<protected>
     */
    public function testCanFetchExistingFileViaFilename()
    {
        $tmp   = $this->_vfsFile();
        $file1 = $this->_object->store($tmp);
        $file2 = $this->_object->fetch($file1->getName());

        $this->assertEquals($file1->getPath(), $file2->getPath());
    }

    /**
     * @covers Core_Storage_Strategy_Hash::fetch
     * @covers Core_Storage_Strategy_Hash::<protected>
     */
    public function testFetchingANonExistingFileWillThrowAnInvalidArgumentException()
    {
        $this->setExpectedException('InvalidArgumentException');

        $this->_object->fetch('no-such-file.txt');
    }

    /**
     * @covers Core_Storage_Strategy_Hash::fetch
     * @covers Core_Storage_Strategy_Hash::<protected>
     */
    public function testFetchingANonReadableFileThrowsARuntimeException()
    {
        $tmp  = $this->_vfsFile();
        $file = $this->_object->store($tmp);

        $this->_vfsChmod($file->getPath(), 0000);
        $this->setExpectedException('RuntimeException');

        $this->_object->fetch($file);
    }

    /**
     * @covers Core_Storage_Strategy_Hash::remove
     * @covers Core_Storage_Strategy_Hash::<protected>
     */
    public function testCanRemoveFile()
    {
        $file = $this->_object->store($this->_vfsFile());
        $this->_object->remove($file);

        $this->assertFalse(file_exists($file->getPath()));
    }

    /**
     * @covers Core_Storage_Strategy_Hash::remove
     * @covers Core_Storage_Strategy_Hash::<protected>
     */
    public function testRemovingANonExistingFileWillThrowAnInvalidArgumentException()
    {
        $this->setExpectedException('InvalidArgumentException');

        $this->_object->remove('no-such-file.txt');
    }

    /**
     * @covers Core_Storage_Strategy_Hash::remove
     * @covers Core_Storage_Strategy_Hash::<protected>
     */
    public function testRemovingAFileFromANonWritableParentThrowsARuntimeException()
    {
        $this->setExpectedException('RuntimeException');
        $tmp  = $this->_vfsFile();
        $file = $this->_object->store($tmp);
        $this->_vfsChmod(dirname($file->getPath()), 0000);

        $this->_object->remove($file);
    }

    /**
     * @covers Core_Storage_Strategy_Hash::createFilePath
     */
    public function testCanCreateFilePath()
    {
        $this->_object->setRoot('/foo');

        $this->assertEquals('/foo/some-file.txt', $this->_object->createFilePath('some-file.txt'));
    }

    /**
     * @covers Core_Storage_Strategy_Hash::createFilePath
     */
    public function testCanCreateFilePathWithDepth()
    {
        $this->_object->setRoot('/foo');
        $this->_object->setDepth(3);

        $this->assertEquals('/foo/s/o/m/some-file.txt', $this->_object->createFilePath('some-file.txt'));
    }

    /**
     * @covers Core_Storage_Strategy_Hash::createFilePath
     */
    public function testCanCreateFilePathWithDepthAndUnicodeFilenameWithSpaces()
    {
        $this->_object->setRoot('/foo');
        $this->_object->setDepth(8);

        // Depth is 8, but spaces are not considered
        $this->assertEquals('/foo/č/ć/ž/š/đ/č ć žšđ.txt', $this->_object->createFilePath('č ć žšđ.txt'));
    }

    /**
     * @covers Core_Storage_Strategy_Hash::createFilePath
     */
    public function testCanCreateFilePathWithDepthWhenFileNameIsShorterThanDepth()
    {
        $this->_object->setRoot('/foo');
        $this->_object->setDepth(5);

        $this->assertEquals('/foo/a/b/c/abc.txt', $this->_object->createFilePath('abc.txt'));
    }

    /**
     * @covers Core_Storage_Strategy_Hash::createFileUrl
     */
    public function testCanCreateFileUrl()
    {
        $this->_object->setDomain('https://cdn1.burza.hr');
        $this->_object->setUrlRoot('/stored/images/');
        $expected = 'https://cdn1.burza.hr/stored/images/some-file.txt';

        $this->assertEquals($expected, $this->_object->createFileUrl('some-file.txt'));
    }

    /**
     * @covers Core_Storage_Strategy_Hash::createFileUrl
     */
    public function testCanCreateFileUrPathlWithDepth()
    {
        $this->_object->setDomain('http://some.random.com');
        $this->_object->setUrlRoot('/some/random/folder/');
        $filename       = 'some-file.txt';

        // depth = 1
        $this->_object->setDepth(1);
        $this->assertEquals('/some/random/folder/s/some-file.txt', $this->_object->createFileUrlPath($filename));

        // depth = 2
        $this->_object->setDepth(2);
        $this->assertEquals('/some/random/folder/s/o/some-file.txt', $this->_object->createFileUrlPath($filename));
    }

    protected function _vfsPath($path = '/')
    {
        $path = trim($path, '/');
        return vfsStream::url('/fakeroot' .($path ? '/'. $path : null));
    }

    protected function _vfsFile($content = 'abc', $ext = 'txt')
    {
        $dir  = $this->_vfsPath('/tmp');
        if (!file_exists($dir)) {
            mkdir($dir);
        }

        $path = $this->_vfsPath('/tmp/'. md5(uniqid()) .'.'. $ext);
        $file = vfsStream::newFile($path);
        file_put_contents($path, $content);
        return $path;
    }

    protected function _vfsChmod($path, $value)
    {
        $path = str_replace(vfsStream::SCHEME .'://', '', $path);

        $item = vfsStreamWrapper::getRoot()->getChild($path)->chmod($value);
        $item->chmod($value);
    }
}
