<?php

namespace Icinga\Module\Director;

use Icinga\Authentication\Auth;
use Icinga\Data\ResourceFactory;
use Icinga\Module\Director\Web\Form\QuickForm;
use Icinga\Exception\NotImplementedError;
use Icinga\Exception\ProgrammingError;
use Icinga\Web\Url;
use Zend_Db_Expr;

class Util
{
    protected static $auth;

    protected static $allowedResources;

    public static function pgBinEscape($binary)
    {
        return new Zend_Db_Expr("'\\x" . bin2hex($binary) . "'");
    }

    public static function hex2binary($bin)
    {
        return pack('H*', $bin);
    }

    public static function binary2hex($hex)
    {
        return current(unpack('H*', $hex));
    }

    /**
     * PBKDF2 - Password-Based Cryptography Specification (RFC2898)
     *
     * This method strictly follows examples in php.net's documentation
     * comments. Hint: RFC6070 would be a good source for related tests
     *
     * @param string $alg        Desired hash algorythm (sha1, sha256...)
     * @param string $secret     Shared secret, password
     * @param string $salt       Hash salt
     * @param int    $iterations How many iterations to perform. Please use at
     *                           least 1000+. More iterations make it slower
     *                           but more secure.
     * @param int    $length     Desired key length
     * @param bool   $raw        Returns the binary key if true, hex string otherwise
     *
     * @return string  A $length byte long key, derived from secret and salt
     */
    public static function pbkdf2($alg, $secret, $salt, $iterations, $length, $raw = false)
    {
        if (! in_array($alg, hash_algos(), true)) {
            throw new NotImplementedError('No such hash algorithm found: "%s"', $alg);
        }

        if ($iterations <= 0 || $length <= 0) {
            throw new ProgrammingError('Positive iterations and length required');
        }

        $hashLength = strlen(hash($alg, '', true));
        $blocks = ceil($length / $hashLength);

        $out = '';

        for ($i = 1; $i <= $blocks; $i++) {
            // $i encoded as 4 bytes, big endian.
            $last = $salt . pack('N', $i);
            // first iteration
            $last = $xorsum = hash_hmac($alg, $last, $secret, true);
            // perform the other $iterations - 1 iterations
            for ($j = 1; $j < $iterations; $j++) {
                $xorsum ^= ($last = hash_hmac($alg, $last, $secret, true));
            }
            $out .= $xorsum;
        }

        if ($raw) {
            return substr($out, 0, $length);
        }

        return bin2hex(substr($out, 0, $length));
    }

    public static function getIcingaTicket($certname, $salt)
    {
        return self::pbkdf2('sha1', $certname, $salt, 50000, 20);
    }

    public static function auth()
    {
        if (self::$auth === null) {
            self::$auth = Auth::getInstance();
        }
        return self::$auth;
    }

    public static function hasPermission($name)
    {
        return self::auth()->hasPermission($name);
    }

    public static function getRestrictions($name)
    {
        return self::auth()->getRestrictions($name);
    }

    public static function resourceIsAllowed($name)
    {
        if (self::$allowedResources === null) {
            $restrictions = self::getRestrictions('director/resources/use');
            $list = array();
            foreach ($restrictions as $restriction) {
                foreach (preg_split('/\s*,\s*/', $restriction, -1, PREG_SPLIT_NO_EMPTY) as $key) {
                    $list[$key] = $key;
                }
            }

            self::$allowedResources = $list;
        } else {
            $list = self::$allowedResources;
        }

        if (empty($list) || array_key_exists($name, $list)) {
            return true;
        }

        return false;
    }

    public static function enumDbResources()
    {
        return self::enumResources('db');
    }

    public static function enumLdapResources()
    {
        return self::enumResources('ldap');
    }

    protected static function enumResources($type)
    {
        $resources = array();
        foreach (ResourceFactory::getResourceConfigs() as $name => $resource) {
            if ($resource->type === $type && self::resourceIsAllowed($name)) {
                $resources[$name] = $name;
            }
        }

        return $resources;
    }

    public static function addDbResourceFormElement(QuickForm $form, $name)
    {
        return self::addResourceFormElement($form, $name, 'db');
    }

    public static function addLdapResourceFormElement(QuickForm $form, $name)
    {
        return self::addResourceFormElement($form, $name, 'ldap');
    }

    protected static function addResourceFormElement(QuickForm $form, $name, $type)
    {
        $list = self::enumResources($type);

        $form->addElement('select', $name, array(
            'label'        => 'Resource name',
            'multiOptions' => $form->optionalEnum($list),
            'required'     => true,
        ));

        if (true && empty($list)) {
            if (self::hasPermission('config/application/resources')) {
                $hint = $form->translate('Please click %s to create new resources');
                $link = sprintf(
                    '<a href="' . Url::fromPath('config/resource') . '" data-base-target="_main">%s</a>',
                    $form->translate('here')
                );
                $form->addHtmlHint(sprintf($hint, $link));
                $msg = sprintf($form->translate('No %s resource available'), $type);
            } else {
                $msg = $form->translate('Please ask an administrator to grant you access to resources');
            }

            $form->getElement($name)->addError($msg);
        }
    }
}
