<?php
/**
 * Copyright © 2012 Alcatel-Lucent.
 *
 * See the NOTICE file distributed with this work for additional information regarding copyright ownership. Licensed to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the specific language governing permissions and limitations under the License.
 */
/**
 * Controller to handle all Policy-related operations
 *
 * Date: 8/22/12
 */

require_once APPLICATION_PATH . "/controllers/JsonPropertyPrinter.php";
require_once APPLICATION_PATH . '/managers/PolicyManager.class.php';
require_once APPLICATION_PATH . '/models/Counter.class.php';
require_once APPLICATION_PATH . '/models/Context.class.php';
require_once APPLICATION_PATH . '/models/Policy.class.php';
require_once APPLICATION_PATH . '/models/Api.class.php';
require_once APPLICATION_PATH . '/models/Auth.class.php';
require_once "flow/FlowController.php";
require_once "SharedViewUtility.php";

class PolicyController extends FlowController {

    /**
     * @var PolicyManager $policyManager
     */
    private $policyManager;
    private $apiList;
    private $authList;

    public function getFlowFile()
    {
        return APPLICATION_PATH."/flows/policy-flow.xml";
    }

    public function init()
    {
        /* Initialize action controller here */
        $this->policyManager = new PolicyManager();
    }

    /**
     * Function to handle index request on /policy
     * Fills out the policy-list view with policies, apis and auths
     * retrieved from E3.
     */
    public function indexAction()
    {
        //        //print_r("indexAction");
        $this->view->messages = $this->getZendFlashMessenger()->getMessages();
        $this->view->apis = $this->policyManager->getAllApis(true);
        $this->view->auths = $this->policyManager->getAllAuths(true);
        $this->view->policies = $this->policyManager->getAllPolicies(true);
    }

    /**
     * Handle the delete-Policy action
     */
    public function deleteAction()
    {
        $this->policyManager->deletePolicy($this->_getParam("id"));
        $this->getZendFlashMessenger()->addMessage(PolicyController::translate("Deleted Policy"));
        $this->_redirect("/policy");
    }

    /**
     * This is the on-enter callback for the "form" state
     * It will load the object needed to back the form.
     * @param $action
     * @param $flowScope
     */
    public function loadFormBacker($action, &$flowScope)
    {
        // Default to using the id from the $flowScope
        $id = @$flowScope['policyId'];
        // If that is empty then we use the one from the request
        if (empty($id)) {
            $id = $this->_getParam("id");
        }
        if (empty($id)) {
            throw new Zend_Controller_Action_Exception(PolicyController::translate('Resource Not Found'), 404);
        }

        // Set the auth and api lists in the flowscope
        // so that the view can load them
        $flowScope['apis'] = $this->policyManager->getAllApis(true);
        $flowScope['auths'] = $this->policyManager->getAllAuths(true);


        // Set the id in the flowscope
        $flowScope['policyId'] = $id;
        $policy = @$flowScope['policy'];

        /**
         * If the id is "create" and we haven't filled out the form before
         * then we need to create a new Policy and set some defaults
         */
        if ($id === "create" && $policy === null) {
            $policy = PolicyController::createBasicPolicy();
            $flowScope['isNew'] = true;
        }
        /**
         * If we are updating and we have not filled out the form before then load it from server
         */
        else if ($policy === null) {
            $policy = $this->policyManager->getPolicy($id);
            $flowScope['isNew'] = false;
            $flowScope["relatedProperties"] = JsonPropertyPrinter::getRelatedFromPolicy($policy);
        }

        /**
         * Put the policy in the flow scope for reference by other states and the views
         */
        $flowScope['policy'] = $policy;
    }

    /**
     * On-exit callback for the "form" state
     * it should take the form submission and deserialize it into a Policy object and
     * stick it on the flowScope.
     *
     * @param $action
     * @param $flowScope
     */
    public function deserializeForm($action, &$flowScope){
        /**
         * @var Policy $policy
         */
        $policy = $flowScope['policy'];

        // Only accept the id if we are creating a new one
        if ($flowScope['isNew']) {
            $policy->id = $this->_getParam('policy_id');
        }

        $policy->setApiIds($this->getSelectedApis());
        $policy->setAuthIds($this->getSelectedAuths());

        $context = $this->getContextAndRates();
        if ($context != null) {
            $contexts[] = $context;
            $policy->setContexts($contexts);
        }

        $policy->headerTransformations = SharedViewUtility::deserializeHeaderTransformations($this->getRequest());
        $policy->properties = SharedViewUtility::deserializeProperties($this->getRequest());
        $policy->tdr = SharedViewUtility::deserializeTdrRules($this->getRequest());

        $flowScope['policy'] = $policy;
    }

    /**
     * on-enter callback for the "submit" state.
     * Should validate the data and then submit the data to server
     *
     * @param $action
     * @param $flowScope
     */
    public function validateFormAndSubmit($action, &$flowScope)
    {
        $translator = PolicyController::getTranslator();
        $validationErrors = array();
        $isNew = $flowScope['isNew'];
        /**
         * @var Policy $policy
         */
        $policy = $flowScope['policy'];

        // Validate policy ID?

        $contexts = $policy->getContexts();
        PolicyController::validateContextAndRates($contexts[0], $validationErrors);

        SharedViewUtility::validateHeaderTransformations($policy->headerTransformations, $validationErrors);
        SharedViewUtility::validateProperties($policy->properties, $validationErrors);
        SharedViewUtility::validateTdrRules($policy->tdr, $validationErrors);

        if (count($validationErrors) > 0) {
            $flowScope['validationErrors'] = $validationErrors;
            return "invalid";
        }

        /**
         * Submit to server
         */
        $success = false;
        if ($isNew) {
            $success = $this->policyManager->createPolicy($policy);
        } else {
            $success = $this->policyManager->updatePolicy($policy);
        }

        if ($success) {
            $message = $isNew ? $translator->translate("Successfully Created Policy") :
                $this->translate("Successfully Updated Policy");
            $this->getZendFlashMessenger()->addMessage($message);
        } else {
            $message = $isNew ? $translator->translate("Error creating policy: ").PolicyManager::error() :
                $translator->translate("Error updating policy: ").PolicyManager::error();
            $validationErrors['default'] = $message;
            $flowScope['validationErrors'] = $validationErrors;
            return "invalid";
        }

//        $flowScope['policyId'] = $policy->getId();

        return "valid";
    }

    private function getContextAndRates()
    {
        $context = PolicyController::getDefaultContext();
        $counter = $this->getCounterForPeriod('per_second');
        if ($counter->getThreshold() > 0 || $counter->getWarning() > 0) {
            $context->setRateLimitPerSecond($counter);
        }
        $counter = $this->getCounterForPeriod('per_minute');
        if ($counter->getThreshold() > 0 || $counter->getWarning() > 0) {
            $context->setRateLimitPerMinute($counter);
        }
        $counter = $this->getCounterForPeriod('per_day');
        if ($counter->getThreshold() > 0 || $counter->getWarning() > 0) {
            $context->setQuotaPerDay($counter);
        }
        $counter = $this->getCounterForPeriod('per_week');
        if ($counter->getThreshold() > 0 || $counter->getWarning() > 0) {
            $context->setQuotaPerWeek($counter);
        }
        $counter = $this->getCounterForPeriod('per_month');
        if ($counter->getThreshold() > 0 || $counter->getWarning() > 0) {
            $context->setQuotaPerMonth($counter);
        }
        return $context;
    }

    private static function validateContextAndRates($context,  &$validationErrors)
    {
        /**
         * @var Context $context
         */
        $success = true;
        $status = $context->getStatus();    // any validation on status?

        $counter = $context->getRateLimitPerSecond();
        if (!empty($counter)) {
            $success = PolicyController::validateCounterForPeriod($counter, 'per_second', $validationErrors);
        }
        $counter = $context->getRateLimitPerMinute();
        if (!empty($counter)) {
            $success &= PolicyController::validateCounterForPeriod($counter, 'per_minute', $validationErrors);
        }
        $counter = $context->getQuotaPerDay();
        if (!empty($counter)) {
            $success &= PolicyController::validateCounterForPeriod($counter, 'per_day', $validationErrors);
        }
        $counter = $context->getQuotaPerWeek();
        if (!empty($counter)) {
            $success &= PolicyController::validateCounterForPeriod($counter, 'per_week', $validationErrors);
        }
        $counter = $context->getQuotaPerMonth();
        if (!empty($counter)) {
            $success &= PolicyController::validateCounterForPeriod($counter, 'per_month', $validationErrors);
        }
        return $success;
    }

    private function getCounterForPeriod($quotaName)
    {
        /**
         * @var Counter $counter
         */
        $counter = new Counter();

        // Validation based on (Drupal-based UI) code from e3_ui_policy_add_context_submit_validate
        if (!empty($_POST[$quotaName]['warning']) || !empty($_POST[$quotaName]['threshold'])) {
            $counter->setAction($_POST[$quotaName]['action']);
            //            $counter->setStatus($_POST[$quotaName]['status']);
            $threshold = !empty($_POST[$quotaName]['threshold']) ? trim($_POST[$quotaName]['threshold']) : "0";
            $warning = !empty($_POST[$quotaName]['warning']) ? trim($_POST[$quotaName]['warning']) : "0";
            $counter->setThreshold($threshold);
            $counter->setWarning($warning);

            if ((strlen($threshold) > 0) && (strlen($warning) == 0)) {
                $counter->setWarning(0.0);
             }
        }
        return $counter;
    }

    private static function validateCounterForPeriod($counter, $quotaName, &$validationErrors)
    {
        /**
         * @var Counter $counter
         */
        $translator = PolicyController::getTranslator();
        $success = true;

        // Validation based on (Drupal-based UI) code from e3_ui_policy_add_context_submit_validate
        $threshold = $counter->getThreshold();
        $warning = $counter->getWarning();
        if (($threshold > 0) || ($warning > 0.0)) {
            $validate_int = new Zend_Validate_Int();
            $validate_gt_0 = new Zend_Validate_GreaterThan(array('min' => 0));
            $validate_gte_0 = new Zend_Validate_GreaterThan(array('min' => -1));

            if (!$validate_int->isValid($threshold) || !$validate_gt_0->isValid($threshold)) {
                $validationErrors[$quotaName] = PolicyController::displayNameForField($quotaName).$translator->translate(" Threshold must be a positive integer if you want to include this Quota. ");
                $success = false;
            } elseif (!$validate_int->isValid($warning) || !$validate_gte_0->isValid($warning)) {
                $validationErrors[$quotaName] = PolicyController::displayNameForField($quotaName).$translator->translate(" Warning must be a non-negative integer. ");
                $success = false;
            } elseif (Zend_Validate::is($warning, 'GreaterThan', array('min' => $threshold))) {
                $validationErrors[$quotaName] = PolicyController::displayNameForField($quotaName).$translator->translate(" Warning may not be greater than the Threshold. ");
                $success = false;
            }
        }
        return $success;
    }

    /**
     * Creates and returns a new Context value for use with a Policy.  Assigns default
     * values to the Id and Status fields.
     *
     * @return Context  A new Context object with default values
     */
    private static function getDefaultContext()
    {
        $context = new Context();
        $context->setId("pctx");
        $context->setStatus("active");
        return $context;
    }

    /**
     * Creates and returns a basic Policy object using the specified parameters.
     * The default value for all arguments is NULL; using NULL arguments will cause
     * the Policy to be created with default (or empty) values.
     *
     * @param $policyId string ID string for the Policy
     * @param $apiIds  array of API IDs to which this Policy applies
     * @param $authIds  array of Auth IDs to which this Policy applies
     * @param $authBucketId string ID to use for the Auth-list bucket
     * @return Policy   A new Policy object
     */
    public static function createBasicPolicy($policyId = NULL, $apiIds = NULL, $authIds = NULL, $authBucketId = NULL)
    {
        $policy = new Policy();

        if (!empty($policyId)) {
            $policy->setId($policyId);
        }

        // Set the policy context by getting and validating rate fields
        $context = PolicyController::getDefaultContext();
        if ($context != null) {
            $contexts[] = $context;
            $policy->setContexts($contexts);
        }
        if (!empty($authIds)) {
            $policy->setApiIds($apiIds);
        }

        $authIdstype = new AuthIdsType();
        if (!empty($authIds)) {
            $authIdstype->authIds = $authIds;
        }
        if (!empty($authBucketId)) {
            $authIdstype->setId($authBucketId);
        }
        $policyAuths[] = $authIdstype;
        $policy->setAuthIds($policyAuths);
        return $policy;
    }

    /**
     * Retrieves the array of user-selected Apis from the latest POST request
     *
     * @return mixed    An array holding the selected Api Ids
     */
    private function getSelectedApis()
    {
        $selectedApis = $this->_getParam('selected_api');
        if (!isset($selectedApis)) {
            $selectedApis = array();
        }
        //$selectedApis = $_POST['selected_api'];
        return $selectedApis;
    }

    /**
     * Retrieves an array with a single AuthIdsType value, which in turn
     * holds an array of selected Auth Ids from the latest POST request
     *
     * @return mixed    An array with an AuthIdsType instance with the array of selected Auth Ids
     */
    private function getSelectedAuths()
    {
        $selectedAuths = array();
        $authIdsType = new AuthIdsType();
        $selectedAuths = $this->_getParam('selected_auth');
        if (!isset($selectedAuths)) {
            $selectedAuths = array();
        }
        $authIdsType->authIds = $selectedAuths;
        $authBucketIdParam = $this->_getParam('auth_bucket_id');
        $authBucketId = isset($authBucketIdParam) ? $authBucketIdParam : null;
        if (!empty($authBucketId)) {
            $authIdsType->setId($authBucketId);
        }
        $selectedAuthIdsTypes[] = $authIdsType;
        return $selectedAuthIdsTypes;
    }

    /**
     * Converts a name value from a form field to a value suitable for display.  Not all
     * form fields have specified display values; in those cases the original name is returned.
     *
     * @param $fieldName    The name value of a Policy detail form field
     * @return string   The display string for the field
     */
    private static function displayNameForField($fieldName)
    {
        $translator = PolicyController::getTranslator();
        switch ($fieldName) {
            case 'per_second':
                return $translator->translate('Per-Second');
            case 'per_minute':
                return $translator->translate('Per-Minute');
            case 'per_hour':
                return $translator->translate('Hourly');
            case 'per_day':
                return $translator->translate('Daily');
            case 'per_week':
                return $translator->translate('Weekly');
            case 'per_month':
                return $translator->translate('Monthly');
        }
        return $fieldName;
    }

    private static function getTranslator() {
        $registry = Zend_Registry::getInstance();
        $translate = $registry->get("Zend_Translate");
        return $translate;
    }

    private static function translate($text) {
        return PolicyController::getTranslator()->translate($text);
    }
}

