/*
 *
 * Copyright (c) 2006 The Apache Software Foundation
 *
 * Licensed 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.
 *
 */

#include "qpid/acl/AclValidator.h"
#include "qpid/acl/AclData.h"
#include "qpid/Exception.h"
#include "qpid/log/Statement.h"
#include "qpid/sys/IntegerTypes.h"
#include <boost/lexical_cast.hpp>
#include <boost/bind.hpp>
#include <numeric>
#include <sstream>

namespace qpid {
namespace acl {

AclValidator::IntPropertyType::IntPropertyType(int64_t i,int64_t j) : min(i), max(j){    
}

bool AclValidator::IntPropertyType::validate(const std::string& val) {
  int64_t v;
  try
  {
    v = boost::lexical_cast<int64_t>(val);
  }catch(const boost::bad_lexical_cast& e){
    return 0;
  }

  if (v < min || v >= max){
    return 0;
  }else{
    return 1;
  }
}

std::string AclValidator::IntPropertyType::allowedValues() {
   return "values should be between " + 
          boost::lexical_cast<std::string>(min) + " and " +
          boost::lexical_cast<std::string>(max);
}

AclValidator::EnumPropertyType::EnumPropertyType(std::vector<std::string>& allowed): values(allowed){      
}

bool AclValidator::EnumPropertyType::validate(const std::string& val) {
  for (std::vector<std::string>::iterator itr = values.begin(); itr != values.end(); ++itr ){
     if (val.compare(*itr) == 0){
        return 1;
     }
  }

  return 0;
}

std::string AclValidator::EnumPropertyType::allowedValues() {
   std::ostringstream oss;
   oss << "possible values are one of { ";
   for (std::vector<std::string>::iterator itr = values.begin(); itr != values.end(); itr++ ){
        oss << "'" << *itr << "' ";
   }
   oss << "}";
   return oss.str();
}

AclValidator::AclValidator(){
  validators.insert(Validator(acl::PROP_MAXQUEUESIZE,
                              boost::shared_ptr<PropertyType>(
                                new IntPropertyType(0,std::numeric_limits<int64_t>::max()))
                             )
                   );

  validators.insert(Validator(acl::PROP_MAXQUEUECOUNT,
                              boost::shared_ptr<PropertyType>(
                                new IntPropertyType(0,std::numeric_limits<int64_t>::max()))
                             )
                   );

  std::string policyTypes[] = {"ring", "ring_strict", "flow_to_disk", "reject"};
  std::vector<std::string> v(policyTypes, policyTypes + sizeof(policyTypes) / sizeof(std::string));
  validators.insert(Validator(acl::PROP_POLICYTYPE,
                              boost::shared_ptr<PropertyType>(new EnumPropertyType(v))
                             )
                   );
  
}

AclValidator::~AclValidator(){
}

/* Iterate through the data model and validate the parameters. */
void AclValidator::validate(boost::shared_ptr<AclData> d) {
  
    for (unsigned int cnt=0; cnt< qpid::acl::ACTIONSIZE; cnt++){

        if (d->actionList[cnt]){

	        for (unsigned int cnt1=0; cnt1< qpid::acl::OBJECTSIZE; cnt1++){

		        if (d->actionList[cnt][cnt1]){

                    std::for_each(d->actionList[cnt][cnt1]->begin(),
                                  d->actionList[cnt][cnt1]->end(),
                                  boost::bind(&AclValidator::validateRuleSet, this, _1));                    
                }//if 
            }//for
	    }//if
	}//for
}

void AclValidator::validateRuleSet(std::pair<const std::string, qpid::acl::AclData::ruleSet>& rules){
    std::for_each(rules.second.begin(),
                  rules.second.end(),
                  boost::bind(&AclValidator::validateRule, this, _1)); 
}

void AclValidator::validateRule(qpid::acl::AclData::rule& rule){
    std::for_each(rule.props.begin(),
                  rule.props.end(),
                  boost::bind(&AclValidator::validateProperty, this, _1)); 
}

void AclValidator::validateProperty(std::pair<const qpid::acl::Property, std::string>& prop){
   ValidatorItr itr = validators.find(prop.first);
    if (itr != validators.end()){
        QPID_LOG(debug,"Found validator for property " << itr->second->allowedValues());

        if (!itr->second->validate(prop.second)){
            throw Exception( prop.second + " is not a valid value for '" + 
                             AclHelper::getPropertyStr(prop.first) + "', " +
                             itr->second->allowedValues());
        } 
    }
}

}}
