<?php

/*
 * Copyright (c) 2015, Leif Lindbäck
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are 
 * met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright 
 *    notice, this list of conditions and the following disclaimer in the 
 *    documentation and/or other materials provided with the distribution.
 * 
 * 3. Neither the name of the copyright holder nor the names of its 
 *    contributors may be used to endorse or promote products derived from 
 *    this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

namespace Id1354fw\Core;

use Id1354fw\Core\ClassLoader;
use Id1354fw\Util\Classes;

/**
 * @author Leif Lindback, leifl@kth.se
 */
class ClassLoaderTest extends \PHPUnit_Framework_TestCase {

    const DEF_HANDLER_NAME = 'DefaultRequestHandler';
    const NAMESPACE_NAME = 'Nspart1';
    const NAMESPACE_PART2_NAME = 'Nspart2';
    const CLASS_NAME = 'TestClass1.php';
    const TEST_CLASSES_DIR = '../tests/resources/classes/';
    const REAL_CLASSES_DIR = '../classes';

    private $pathToRealClassesDir;

    private $loader;

    protected function setUp() {
        $this->loader = ClassLoader::getInstance();

        \chdir(self::REAL_CLASSES_DIR);
        $this->pathToRealClassesDir = \getcwd();
        \symlink(self::TEST_CLASSES_DIR . self::NAMESPACE_NAME, self::NAMESPACE_NAME);
        \symlink(self::TEST_CLASSES_DIR . self::CLASS_NAME, self::CLASS_NAME);
    }

    protected function tearDown() {
        \chdir($this->pathToRealClassesDir);
        \unlink(self::NAMESPACE_NAME);
        \unlink(self::CLASS_NAME);
    }

    public function testLoadClass() {
        $this->loadExistingClass('TestClass1');
        $this->loadExistingClass('Nspart1\\TestClass1');
        $this->loadExistingClass('Nspart1\\Nspart2\\TestClass1');
        $this->loadExistingClass('Nspart1\\Nspart2\\Test_Class1');
        $this->loadExistingClass('Nspart1\\Nspart2\\TestClass3');
        $this->loadNonExistingClass('Nspart1\\testClass1');
        $this->loadNonExistingClass('nspart1\\TestClass1');
        $this->loadNonExistingClass('Abc');
        $this->loadNonExistingClass('Abc\\Abc');
        $this->loadNonExistingClass('Abc\\TestClass1');
        $this->loadNonExistingClass('Nspart1\\Abc');
        $this->loadClassWithInvalidName('');
        $this->loadClassWithInvalidName('Nspart1\\..\\Nspart1\\TestClass1');
    }

    private function loadExistingClass($className) {
        try {
            $this->loader->loadClass($className);
        } catch (ClassNotFoundException $cnfe) {
            $this->fail('Could not load ' . $className . ', ' . $cnfe->getMessage());
        }
    }

    private function loadNonExistingClass($className) {
        try {
            $this->loader->loadClass($className);
            $this->fail('Loaded non existing class: ' . $className);
        } catch (ClassNotFoundException $cnfe) {
            return;
        }
        $this->fail('Wrong exception for non existing class: ' . $className);
    }

    private function loadClassWithInvalidName($className) {
        try {
            $this->loader->loadClass($className);
            $this->fail('Loaded class with invalid name: ' . $className);
        } catch (\InvalidArgumentException $iae) {
            return;
        }
        $this->fail('Wrong exception for class with invalid name: ' . $className);
    }

    public function testLoadDefaultHandler() {
        try {
            $currDir = \getcwd();
            $this->loader->loadDefaultHandler();
            $this->fail('Loaded default handler when there was none.');
        } catch (ClassNotFoundException $cnfe) {
            $this->assertEquals($currDir, \getcwd(), 'Wrong directory after loading def handler.');
        }
        $this->defaultHandlerAt(self::NAMESPACE_NAME, self::NAMESPACE_NAME);
        $this->defaultHandlerAt('.', '');
        $this->defaultHandlerAt(self::NAMESPACE_NAME . DIRECTORY_SEPARATOR . self::NAMESPACE_PART2_NAME,
                                   self::NAMESPACE_NAME . Classes::NAMESPACE_SEPARATOR .
                self::NAMESPACE_PART2_NAME);
    }

    private function defaultHandlerAt($pathToDefHandler, $namespace) {
        $defHandlerFile = $pathToDefHandler . DIRECTORY_SEPARATOR . self::DEF_HANDLER_NAME . ".php";
        $this->createDefaultHandler($defHandlerFile, $namespace);
        try {
            $foundClass = $this->loader->loadDefaultHandler();
            if (\strlen($namespace) > 0) {
                $className = $namespace . Classes::NAMESPACE_SEPARATOR . self::DEF_HANDLER_NAME;
            } else {
                $className = self::DEF_HANDLER_NAME;
            }
            $this->assertEquals($className, \strstr($foundClass, $className),
                                                    'Found wrong default handler.');
        } catch (\InvalidArgumentException $iae) {
            $this->assertContains($pathToDefHandler . DIRECTORY_SEPARATOR . self::DEF_HANDLER_NAME,
                                  $iae->getMessage());
        } catch (ClassNotFoundException $cnfe) {
            $this->fail('Could not find default handler, ' . $cnfe->getMessage());
        } finally {
            \unlink($defHandlerFile);
        }
    }

    private function createDefaultHandler($defHandlerFile, $namespace) {
        $defHandlerClass = self::DEF_HANDLER_NAME;
        $defHandlerContent = <<<EOC
namespace $namespace;
class $defHandlerClass {}

EOC;
        \file_put_contents($defHandlerFile, $defHandlerContent);
    }

}
