<?php

/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; under version 2
 * of the License (non-upgradable).
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * Copyright (c) 2008-2010 (original work) Deutsche Institut für Internationale Pädagogische Forschung (under the project TAO-TRANSFER);
 *               2009-2012 (update and modification) Public Research Centre Henri Tudor (under the project TAO-SUSTAIN & TAO-DEV);
 *
 */
use oat\tao\test\TaoPhpUnitTestRunner;
use \oat\generis\model\user\PasswordConstraintsService;

/**
 * This class enable you to test the validators
 *
 * @author Joel Bout, <taosupport@tudor.lu>
 * @package tao

 */
class ValidatorTest extends TaoPhpUnitTestRunner
{

    public static function staticMirror($value)
    {
        return $value;
    }

    /**
     * tests initialization
     */
    public function setUp(): void
    {
        TaoPhpUnitTestRunner::initTest();
    }

    /**
     * Test the service factory: dynamical instantiation and single instance serving
     * @see tao_models_classes_ServiceFactory::get
     */
    public function testAlphaNum()
    {

        //@todo  fix "\n" in validator and add to test

        // test getValidator
        $alphanum = tao_helpers_form_FormFactory::getValidator('AlphaNum');
        $this->assertIsA($alphanum, 'tao_helpers_form_validators_AlphaNum');

        $alphanum       = new tao_helpers_form_validators_AlphaNum();
        $this->exec(
            $alphanum,
            ['abc123', '', 'Ab1Cd2Ef3', 50],
            [null, 'a_1', '!', '&auml;', " ", [], 12.3],
            'AlphaNum without punctuation'
        );

        $alphanumpunct  = new tao_helpers_form_validators_AlphaNum(['allow_punctuation' => true]);

        $this->exec(
            $alphanumpunct,
            ['abc123', '', 'Ab1Cd2Ef3','a_1','1-2-3-4', 12],
            [null, '!', '&auml;', '/root/test/why', '1.23', '2,5', []],
            'AlphaNum with punctuation'
        );
    }

    public function exec(tao_helpers_form_Validator $pValidator, $pValid, $pInvalid = [], $pHint = '')
    {
        $this->validValues($pValidator, is_array($pValid) ? $pValid : [$pValid], $pHint);
        $this->invalidValues($pValidator, is_array($pInvalid) ? $pInvalid : [$pInvalid], $pHint);
    }

    public function validValues(tao_helpers_form_Validator $pValidator, $pValues, $pHint = '')
    {
        $desc = empty($pHint) ? get_class($pValidator) : $pHint;
        foreach ($pValues as $val) {
            $nfo = $val;
            if (is_array($val) && isset($val['name'])) {
                $nfo = $val['name'];
            }
            $this->assertTrue($pValidator->evaluate($val), $desc . ' evaluated \'' . $nfo . '\' as false');
        }
    }

    public function invalidValues(tao_helpers_form_Validator $pValidator, $pValues, $pHint = '')
    {
        $desc = empty($pHint) ? get_class($pValidator) : $pHint;
        foreach ($pValues as $val) {
            $nfo = $val;
            if (is_array($val) && isset($val['uploaded_file'])) {
                $nfo = $val['uploaded_file'];
            }
            if (is_array($nfo)) {
                $nfo = implode('-', $nfo);
            }
            $this->assertFalse($pValidator->evaluate($val), $desc . ' evaluated \'' . $nfo . '\' as true');
        }
    }

    public function testCallback()
    {

        // global function
        // wrong parameters
        $callback = new tao_helpers_form_validators_Callback([
            'function' => 'aFunctionThatDoesntExist'
        ]);
        $this->expectException(common_Exception::class);
        $this->assertFalse($callback->evaluate(''));

        // global function
        // simple parameters
        $callback = new tao_helpers_form_validators_Callback([
            'function' => 'ValidatorTestCaseGlobalMirror'
        ]);
        $this->assertTrue($callback->evaluate(true));
        $this->assertFalse($callback->evaluate(false));

        // global function
        // complex parameters
        $callback       = new tao_helpers_form_validators_Callback([
            'function' => 'ValidatorTestCaseGlobalInstanceOf'
        ]);
        $this->assertTrue($callback->evaluate([
                'tao_helpers_form_validators_Callback' => $callback
        ]));
        $this->assertFalse($callback->evaluate([
                'tao_helpers_form_validators_AlphaNum' => $callback
        ]));

        // static function
        $callback = new tao_helpers_form_validators_Callback([
            'class'     => 'ValidatorTestCasePrototype',
            'method'    => 'staticMirror'
        ]);
        $this->assertTrue($callback->evaluate(true));
        $this->assertFalse($callback->evaluate(false));

        // static function
        $callback = new tao_helpers_form_validators_Callback([
            'object'    => $this,
            'method'    => 'instanceMirror'
        ]);
        $this->assertTrue($callback->evaluate(true));
        $this->assertFalse($callback->evaluate(false));
    }

    public function testDateTime()
    {

        //@todo:  doublecheck empty string and null treatment

        $dateTime = new tao_helpers_form_validators_DateTime();
        $this->exec(
            $dateTime,
            ['April 17, 1790', '2008-07-01T22:35:17.03+08:00', '10/Oct/2000:13:55:36 -700', 'today', '04:08', 'a week ago', 'yesterday', 'tomorrow'],
            ['abc'],
            'simple Datetimes'
        );

        $formelement = new tao_helpers_form_elements_xhtml_Calendar('testelement');
        $formelement->setValue('today');

        // config sanity tests
        //      $this->expectException();
        //      $dateTime = new tao_helpers_form_validators_DateTime(array(
        //              'comparator'    => 'nonsense',
        //              'datetime2_ref' => $formelement
        //      ));
        //      $dateTime->evaluate('today');

        //      $this->expectException();
        //      $dateTime = new tao_helpers_form_validators_DateTime(array(
        //              'datetime2_ref' => 'test'
        //      ));
        //      $dateTime->evaluate('today');

        //      $this->expectException();
        //      $dateTime = new tao_helpers_form_validators_DateTime(array(
        //              'comparator'    => 'less',
        //      ));
        //      $dateTime->evaluate('today');

        $dateTime = new tao_helpers_form_validators_DateTime([
            'comparator'    => 'after',
            'datetime2_ref' => $formelement
        ]);
        $this->exec($dateTime, 'tomorrow', 'yesterday', 'Compare After');

        $dateTime = new tao_helpers_form_validators_DateTime([
            'comparator'    => '<',
            'datetime2_ref' => $formelement
        ]);
        $this->exec($dateTime, 'yesterday', 'tomorrow', 'Compare After');
    }

    public function testFileMimeType()
    {
        //XML
        $val = ['uploaded_file' => dirname(__FILE__) . '/samples/events.xml'];
        $filemime = new tao_helpers_form_validators_FileMimeType([
                'mimetype' => ['text/xml', 'application/xml', 'application/x-xml'],
                'extension' => ['xml']
        ]);
        $this->assertTrue($filemime->evaluate($val));

        //ZIP
        $val = ['uploaded_file' => dirname(__FILE__) . '/samples/zip/test.zip'];
        $filemime = new tao_helpers_form_validators_FileMimeType([
                'mimetype' => ['application/zip'],
                'extension' => ['zip']
        ]);
        $this->assertTrue($filemime->evaluate($val));

        //CSS
        $val = ['uploaded_file' => dirname(__FILE__) . '/samples/css/test.css'];
        $filemime = new tao_helpers_form_validators_FileMimeType([
                'mimetype' => ['text/css', 'text/plain'],
                'extension' => ['css']
        ]);
        $this->assertTrue($filemime->evaluate($val));

        //Error
        $val = ['uploaded_file' => dirname(__FILE__) . '/samples/sample_sort.po'];
        $filemime = new tao_helpers_form_validators_FileMimeType([
                'mimetype' => ['text/css'],
                'extension' => ['po']
        ]);
        $this->assertFalse($filemime->evaluate($val));
    }

    public function testFileSize()
    {

        $smallfile = [
                'name'     => 'testname',
                'tmp_name' => '/tmp/doesnotexists',
                'error'    => UPLOAD_ERR_OK,
                'size'     => 500,
        ];
        $mediumfile = [
                'name'     => 'testname',
                'tmp_name' => '/tmp/doesnotexists',
                'error'    => UPLOAD_ERR_OK,
                'size'     => 1000000,
        ];
        $bigfile = [
                'name'     => 'testname',
                'tmp_name' => '/tmp/doesnotexists',
                'error'    => UPLOAD_ERR_OK,
                'size'     => 50000000,
        ];
        $errorfile = [
                'error'    => UPLOAD_ERR_NO_FILE,
        ];

        //option test
        $this->expectException(common_Exception::class);
        $filemime = new tao_helpers_form_validators_FileSize([]);

        $filesize = new tao_helpers_form_validators_FileSize(['min' => 1000]);
        $this->exec($filemime, [$mediumfile, $bigfile], [$errorfile, $smallfile], 'Filesize Minimum Validation');

        $filesize = new tao_helpers_form_validators_FileSize(['max' => 1000]);
        $this->exec(
            $filemime,
            [$smallfile],
            [$errorfile, $mediumfile, $bigfile],
            'Filesize Maximum Validation'
        );

        $filesize = new tao_helpers_form_validators_FileSize(['min' => 1000, 'max' => 5000000]);
        $this->exec(
            $filemime,
            [$mediumfile],
            [$errorfile, $smallfile, $bigfile],
            'Filesize Range Validation'
        );
    }

    public function testNumeric()
    {
        $num = tao_helpers_form_FormFactory::getValidator('Numeric');
        $this->assertIsA($num, 'tao_helpers_form_validators_Numeric');

        $num = new tao_helpers_form_validators_Numeric();
        $this->exec(
            $num,
            ['10', '10.1', 12, 12.1],
            ['a_1', '!', '&auml;'], //TODO null, " ", array() with a refactoring to include noempty as a mother class
            'Numeric validation'
        );

        $num = new tao_helpers_form_validators_Integer();
        $this->exec(
            $num,
            ['10', 12],
            ['10.1', 12.1],
            'Integer validation'
        );

        $num = tao_helpers_form_FormFactory::getValidator('Integer', ['min' => 10]);
        $this->assertFalse($num->evaluate(5));
        $this->assertTrue($num->evaluate(11));

        $elt = tao_helpers_form_FormFactory::getElement('max', 'Textbox');
        $elt->setValue('5');

        $num = tao_helpers_form_FormFactory::getValidator('Integer', ['integer2_ref' => $elt, 'comparator' => '>']);
        $this->exec(
            $num,
            [10, 102],
            [2, -40],
            'Integer comparator validation'
        );
    }

    public function testLabel()
    {
        //@todo implement test cases
    }

    public function testLength()
    {
        $minlenght = new tao_helpers_form_validators_Length(['min' => 3]);
        $this->exec(
            $minlenght,
            ['abc', '1234', '___', '   '],
            ['!', "qc", "  ", ""],
            'Length with min 3'
        );

        $maxlenght = new tao_helpers_form_validators_Length(['max' => 3]);
        $this->exec(
            $maxlenght,
            ['abc', '12', '_', '   ', '','!'],
            ["qcde",'    '],
            'Length with max 3'
        );

        $minmaxlenght = new tao_helpers_form_validators_Length(['min' => 2, 'max' => 4]);
        $this->exec(
            $minmaxlenght,
            ['ab', '123', '____', '   '],
            ['!', "q", "qq  q", ""],
            'Length with min 2 max 4'
        );

        $utf8 = 'ää';
        $umls = iconv("UTF-8", mb_internal_encoding(), $utf8);
        $this->assertFalse($minlenght->evaluate($umls), 'Error during length validation of special characters \'' . $utf8 . '\' using encoding ' . mb_internal_encoding());
    }

    /**
     * @dataProvider notEmptyProvider
     */
    public function testNotEmpty($value, $expected)
    {

        $validator = tao_helpers_form_FormFactory::getValidator('NotEmpty');
        $this->assertEquals($expected, $validator->evaluate($value));
    }

    public function notEmptyProvider()
    {
        $validObject   = new \stdClass();
        $validObject->data = 'valid_object';

        return [
            'valid_string'   => ['valid_string', true],
            'valid_array'    => [['valid_array'],  true],
            'valid_int'      => [1, true],
            'valid_int_zero' => [1, true],
            'valid_float'      => [1.5, true],
            'valid_float_zero' => [0.0, true],
            'valid_object'     => [$validObject, true],
            'valid_bool_t'     => [true, true],
            'valid_bool_f'     => [false, true],

            'invalid_string'   => ['', false],
            'invalid_array'    => [[],  false],
            'invalid_null'     => [null, false],
        ];
    }

    public function testEquals()
    {
        $formelement = new tao_helpers_form_elements_xhtml_Textbox('testelement');
        $formelement->setValue('123');

        $equals = tao_helpers_form_FormFactory::getValidator('Equals', [
            'reference' => $formelement
        ]);
        $this->assertIsA($equals, 'tao_helpers_form_validators_Equals');

        $this->assertFalse($equals->evaluate('1234'));
        $this->assertTrue($equals->evaluate('123'));

        $equals = tao_helpers_form_FormFactory::getValidator('Equals', [
            'reference' => $formelement,
            'invert'    => true
        ]);
        $this->assertIsA($equals, 'tao_helpers_form_validators_Equals');

        $this->assertFalse($equals->evaluate('123'));
        $this->assertTrue($equals->evaluate('1234'));

        $this->expectException(common_Exception::class);
        $equals = tao_helpers_form_FormFactory::getValidator('Equals');
        //@todo implement test cases for multivalues
    }

    /**
     * Positive test for tao_helpers_form_validators_Password
     */
    public function testPasswordPositive()
    {
        $validPassword = 'valid_top_secret_test_password';
        $invalidPassword = 'invalid_top_secret_test_password';

        $formElement = new tao_helpers_form_elements_xhtml_Password('testelement');
        $formElement->setValue($validPassword);

        $validator = tao_helpers_form_FormFactory::getValidator('Password', [
            'password2_ref' => $formElement
        ]);

        $this->assertInstanceOf('tao_helpers_form_Validator', $validator);
        $this->assertTrue($validator->evaluate($validPassword));
        $this->assertFalse($validator->evaluate($invalidPassword));
    }

    public function testPasswordNegativeNoReference()
    {
        $this->expectException(common_Exception::class);
        $this->expectExceptionMessage('Please set the reference of the second password element');
        $validator = tao_helpers_form_FormFactory::getValidator('Password');
        $validator->evaluate('something');
    }

    public function testPasswordNegativeInvalidReference()
    {
        $this->expectException(common_Exception::class);
        $this->expectExceptionMessage('Please set the reference of the second password element');
        $validator = tao_helpers_form_FormFactory::getValidator('Password', ['password2_ref' => 'invalid_form_element']);
        $validator->evaluate('something');
    }

    /**
     * @param string $value
     * @param string $format
     * @param bool $expected
     *
     * @dataProvider regexProvider
     */
    public function testRegex($value, $format, $expected)
    {
        $validator = tao_helpers_form_FormFactory::getValidator('Regex', [
            'format' => $format
        ]);

        $this->assertInstanceOf('tao_helpers_form_Validator', $validator);
        $this->assertEquals($expected, $validator->evaluate($value));
    }

    public function testRegexMisconfig()
    {
        $this->expectException(common_Exception::class);
        $this->expectExceptionMessage('Please set the format options (define your regular expression)!');
        tao_helpers_form_FormFactory::getValidator('Regex', []);
    }

    public function regexProvider()
    {
        return [
            'valid'   => ['aaaaababaaab','/[ab]+/',true],
            'invalid' => ['cdcdcdcdcddd','/[ab]+/',false],
        ];
    }

    /**
     * @param $value
     * @param $allowParams
     * @param $expected
     *
     * @dataProvider urlProvider
     */
    public function testUrl($value, $allowParams, $expected)
    {
        $options = [];

        if ($allowParams === true) {
            $options['allow_parameters'] = true;
        }

        $validator = tao_helpers_form_FormFactory::getValidator('Url', $options);
        $this->assertInstanceOf('tao_helpers_form_Validator', $validator);
        $this->assertEquals($expected, $validator->evaluate($value));
    }

    public function urlProvider()
    {
        return [
            'valid_short'       => ['http://example.com', false, true],
            'valid_port'        => ['http://example.com:1234', false, true],
            'valid_ip'          => ['http://127.0.0.1', false, true],
            'valid_ip_port'     => ['http://127.0.0.1:1234', false, true],
            'valid_proto'       => ['ftp://example.com', false, true],
            'valid_subdomain'   => ['http://subdomain.example.com', false, true],
            'valid_full'        => ['http://example.com/foo/bar.html', false, true],
            'valid_short_path'  => ['http://example.com/foo/bar', false, true],
            'valid_full_params' => ['http://example.com/foo/bar.html?param=1', true, true],
            //--------
            'invalid_not_url'   => ['not_an_url', false, false],
            'invalid_url_1'     => ['http//example.com', false, false],
            'invalid_url_2'     => ['http:/example.com', false, false],
            'invalid_url_3'     => ['http://example,com', false, false],
            'invalid_long_port' => ['http://example,com:234242', false, false],
            'invalid_no_params' => ['http://example.com/?param=restricted', false, false],
        ];
    }

    public function testPasswordStrength()
    {
        $validator = tao_helpers_form_FormFactory::getValidator('PasswordStrength');

        $this->assertInstanceOf('tao_helpers_form_Validator', $validator);
        $this->assertTrue($validator->evaluate($this->buildValidPassword()));
        $this->assertFalse($validator->evaluate($this->buildInvalidPassword()));
    }

    //Helpers

    protected function buildValidPassword()
    {
        $validPassword = '';

        $constaintsService = PasswordConstraintsService::singleton();

        $config = $this->invokeProtectedMethod($constaintsService, 'getConfig');

        if ($config['upper'] === true) {
            $validPassword .= 'A';
        }

        if ($config['lower'] === true) {
            $validPassword .= 'b';
        }

        if ($config['number'] === true) {
            $validPassword .= '3';
        }

        if ($config['spec'] === true) {
            $validPassword .= '@';
        }

        $validPassword = str_pad($validPassword, $config['length'], 'c');

        return $validPassword;
    }

    protected function buildInvalidPassword()
    {
        $constaintsService = PasswordConstraintsService::singleton();
        $config = $this->invokeProtectedMethod($constaintsService, 'getConfig');

        $invalidPassword = $this->buildValidPassword();

        if ($config['upper'] === true) {
            $invalidPassword = str_replace('A', '', $invalidPassword);
        }

        if ($config['lower'] === true) {
            $invalidPassword = str_replace('b', '', $invalidPassword);
        }

        if ($config['number'] === true) {
            $invalidPassword = str_replace('3', '', $invalidPassword);
        }

        if ($config['spec'] === true) {
            $invalidPassword = str_replace('@', '', $invalidPassword);
        }

        $invalidPassword = substr($invalidPassword, 0, $config['length'] - 1);

        return $invalidPassword;
    }

    public function testUnique()
    {
        $resourceMock = $this->getMockBuilder('core_kernel_classes_Class')->disableOriginalConstructor()->getMock();
        $resourceMock->method('getParentClasses')->willReturn([new kernel_class_Stub()]);

        $validator = tao_helpers_form_FormFactory::getValidator('Unique');
        $this->assertInstanceOf('tao_helpers_form_Validator', $validator);

        $options = [
            'property'      => new kernel_property_Stub(kernel_class_Stub::TEST_PROPERTY_NAME),
        ];
        $validator->setOptions($options);

        $this->assertTrue($validator->evaluate(kernel_class_Stub::PROPERTY_NOT_EXISTS));
        $this->assertFalse($validator->evaluate(kernel_class_Stub::PROPERTY_EXISTS_FIRST_LEVEL));
        $this->assertFalse($validator->evaluate(kernel_class_Stub::PROPERTY_EXISTS_RECURSIVE));
    }

    public function testUniqueNegativePropertyNotSet()
    {
        $this->expectException(common_exception_Error::class);
        $this->expectExceptionMessage('Property not set');
        $resourceMock = $this->getMockBuilder('core_kernel_classes_Class')->disableOriginalConstructor()->getMock();
        $resourceMock->method('getParentClasses')->willReturn([new kernel_class_Stub()]);

        $options = [
            'resourceClass' => $resourceMock,
        ];

        $validator = tao_helpers_form_FormFactory::getValidator('Unique', $options);
        $validator->evaluate('some value');
    }

    public function instanceMirror($value)
    {
        return $value;
    }
}

class kernel_class_Stub
{

    const TEST_PROPERTY_NAME          = 'testProperty';
    const PROPERTY_EXISTS_FIRST_LEVEL = 'exists_first_level';
    const PROPERTY_EXISTS_RECURSIVE   = 'exists_recursive';
    const PROPERTY_NOT_EXISTS         = 'not_exists';

    public function searchInstances($propertyFilters = [], $options = [])
    {
        $recursive = isset($options['recursive']) ? $options['recursive'] : false;

        $returnValue = [];

        switch ($propertyFilters[self::TEST_PROPERTY_NAME]) {
            case self::PROPERTY_EXISTS_FIRST_LEVEL: {
                $returnValue[] = 'some_found_value';
                break;
            }
            case self::PROPERTY_EXISTS_RECURSIVE: {
                if ($recursive === true) {
                    $returnValue[] = 'some_found_value';
                }
                break;
            }
        }

        return $returnValue;
    }
}

class kernel_property_Stub extends core_kernel_classes_Property
{
    public function getDomain()
    {
        return [ new kernel_class_Stub() ];
    }
}

//Global function
function ValidatorTestCaseGlobalInstanceOf($values)
{
    $return = true;
    foreach ($values as $class => $object) {
        if (!$object instanceof $class) {
            $return = false;
        }
    }
    return $return;
};
function ValidatorTestCaseGlobalMirror($values)
{
    return $values;
};