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

/**
 * @category  Core
 * @package   Core_User
 * @copyright Copyright (c) 2011. Burza d.o.o. (http://web.burza.hr/en/)
 * @license   proprietary
 */
class Core_User_AuthAdapter_Doctrine implements Core_User_AuthAdapter_Interface
{
    /**
     * Identity discovery service
     *
     * @var Core_User_IdentityDiscovery_Interface
     */
    protected $_identityDiscovery;

    /**
     * Base doctrine query
     *
     * @var Doctrine_Query
     */
    protected $_query;

    /**
     * Array with fields for query
     *
     * @var array
     */
    protected $_fields;

    /**
     * Array with values
     *
     * @var array
     */
    protected $_values;

    /**
     * Array with constraints
     *
     * @var array
     */
    protected $_constraints = array();

    /**
     *
     * @return Core_User_IdentityDiscovery_Interface
     */
    public function getIdentityDiscovery()
    {
        if (null === $this->_identityDiscovery) {
            throw new Exception('Error fetching identity discovery for auth adapter');
        }

        return $this->_identityDiscovery;
    }

    /**
     *
     * @param Core_User_IdentityDiscovery_Interface $identityDiscovery
     *
     * @return Core_User_AuthAdapter_Doctrine
     */
    public function setIdentityDiscovery(Core_User_IdentityDiscovery_Interface $identityDiscovery)
    {
        $this->_identityDiscovery = $identityDiscovery;

        return $this;
    }

    /**
     * Get query
     *
     * @return Doctrine_Query
     */
    public function getQuery()
    {
        if (null === $this->_query) {
            $this->_query = $this->getIdentityDiscovery()
                ->createQuery('u')->select('u.*');
        }

        return $this->_query;
    }

    /**
     * Get array with fields
     *
     * @return array
     */
    public function getFields()
    {
        return $this->_fields;
    }

    /**
     * Set array with fields
     *
     * @param array $fields
     *
     * @return Core_User_AuthAdapter_Basic
     */
    public function setFields(array $fields)
    {
        $this->_fields = $fields;

        return $this;
    }

    /**
     * Add field
     *
     * @param string         $name
     * @param string|null    $modelName
     *
     * @return Core_User_AuthAdapter_Basic
     */
    public function addField($name, $modelName = null)
    {
        $this->_fields[$name] = array(
            'name'          => $name,
            'model_name'    => isset($modelName) ? $modelName : $name
        );

        return $this;
    }

    /**
     * Get all values
     *
     * @return array
     */
    public function getValues()
    {
        return $this->_values;
    }

    /**
     * Set values
     *
     * @param array $values
     * @return \Core_User_AuthAdapter_Basic
     */
    public function setValues($values)
    {
        $this->_values = $values;

        return $this;
    }

    /**
     * Get field value
     *
     * @param array $field
     *
     * @return mixed Value from values array
     * @throws Exception
     */
    public function getValue(array $field)
    {
        if (!isset($this->_values[$field['name']])) {
            throw new Exception(sprintf(
                'Value not set for field %s', $field['name']
            ));
        }

        return $this->_values[$field['name']];
    }

    /**
     * Get constraints
     *
     * @return array
     */
    public function getConstraints()
    {
        return $this->_constraints;
    }

    /**
     * Set constraints
     *
     * @param array $constraints
     *
     * @return Core_User_AuthAdapter_Basic
     */
    public function setConstraints(array $constraints)
    {
        $this->_constraints = $constraints;

        return $this;
    }


    /**
     * Add constraint
     *
     * @param string         $name
     * @param string|null    $modelName
     * @param string|boolean $preFilter
     *
     * @return Core_User_AuthAdapter_Basic
     */
    public function addConstraint($name, $modelName = null, $preFilter = false)
    {
        $this->_constraints[$name] = array(
            'name'          => $name,
            'model_name'    => isset($modelName) ? $modelName : $name,
            'prefilter'     => $preFilter
        );

        return $this;
    }

    /**
     * Get constraint value, prefiltering if need be
     *
     * @param array           $constraint
     * @param Doctrine_Record $entity
     *
     * @return mixed Value from values array
     * @throws Exception
     */
    public function getConstraintValue(array $constraint, Doctrine_Record $entity = null)
    {
        if (!isset($this->_values[$constraint['name']])) {
            throw new Exception(sprintf(
                'Value not set for field %s', $constraint['name']
            ));
        }

        $value = $this->_values[$constraint['name']];

        if ($constraint['prefilter'] !== false) {
            if (strpos($constraint['prefilter'], '::') !== false) {
                // Class::Method
                $prefilter = $constraint['prefilter'];
            } elseif ($entity !== null && is_callable(array($entity, $constraint['prefilter']))) {
                // array(Current entity, method)
                $prefilter = array($entity, $constraint['prefilter']);
            } elseif (function_exists($constraint['prefilter'])) {
                // any function
                $prefilter = $constraint['prefilter'];
            } else {
                // No function, not callable
                throw new Exception(sprintf('Constraint prefilter "%s" does not exist or is not callable'));
            }

            $value = call_user_func($prefilter, $value);
        }

        return $value;
    }

    /**
     * Authenticate
     *
     * @return \Zend_Auth_Result
     */
    public function authenticate()
    {
        $fields = $this->getFields();

        foreach ($fields as $field) {
            $value = $this->getValue($field);
            $this->getQuery()
                ->andWhere(sprintf('u.%s = ?', $field['model_name']), $value);
        }

        $entity = $this->getQuery()->limit(1)->fetchOne();

        if ($this->getQuery()->count() > 1) {
            // More than one result
            return new Zend_Auth_Result(
                Zend_Auth_Result::FAILURE_IDENTITY_AMBIGUOUS,
                null,
                array('More than one user found')
            );
        }

        if (!$entity) {
            // No results
            return new Zend_Auth_Result(
                Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND,
                null,
                array('User not found')
            );
        }

        // Constraints
        $constraints = $this->getConstraints();
        $entityValues = $entity->toArray();
        foreach ($constraints as $constraint) {
            $value = $this->getConstraintValue($constraint, $entity);

            // Don't do typesafe comparison
            if ($entityValues[$constraint['model_name']] != $value) {
                // One of constraints failed, bad auth
                return new Zend_Auth_Result(
                    Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID,
                    $entity,
                    array('Bad credentials')
                );
            }
        }

        // All is good
        return new Zend_Auth_Result(
            Zend_Auth_Result::SUCCESS,
            $entity
        );
    }
}