//! \addtogroup Val Validation
//!
//! 
//! @{
#ifndef __WB_VALIDATION_H__
#define __WB_VALIDATION_H__

#include <vector>
#include <string>
#include <map>
#include <iostream>
#include "boost/shared_ptr.hpp"
#include "boost/bind.hpp"

//#define VALIDATION_DEBUG 1
#ifdef VALIDATION_DEBUG
#include <typeinfo>
#define dprint(...) fprintf(stderr, __VA_ARGS__)
#define dprint_type(T) printf("%s", typeid(T).name())
#else
#define dprint(...)  
#define dprint_type(T)
#endif

class ResultsList;

typedef std::vector<std::string> StringList;

namespace val
{

//=============================================================================
//
//=============================================================================
/*!
    \class ChainBase
    \brief Base class which allows ChainsSet to operate on sets of checks
    \ingroup Val
 */
class ChainBase
{
  public:
    typedef boost::shared_ptr<ChainBase> Ptr;
    virtual ~ChainBase() {};
};

//=============================================================================
//
//=============================================================================
/*! \class ChainsSet 
    \brief Constains and manages sets of validations
    \ingroup Val
*/
class ChainsSet
{
  public:
    
    void set_chain(const char* type, ChainBase::Ptr vc)
    {
      dprint("ChainsSet::set_chain('%s', %p)\n", type, vc.get());
      chains[type] = vc;
      //const int chainss = chains.size();
    }
    
    void clear()
    {
      chains.clear();
    }
  private:
    typedef std::map<std::string, ChainBase::Ptr> ChainsMap;
    ChainsMap  chains;
    
  public:
      // Using this method directly is not advised. See Chain<T>::chain instead
    ChainBase::Ptr get_chain(const char* type)
    {
      #ifdef VALIDATION_DEBUG
      dprint("ChainsSet::get_chain(%s)\n", type);
      ChainsMap::iterator cur  = chains.begin();
      ChainsMap::iterator last = chains.end();
      for (; cur != last; cur++)
      {
        dprint("\tchains[%s] = %p\n", cur->first.c_str(), cur->second.get());
      }
      #endif
      ChainBase::Ptr vcb;
      ChainsMap::iterator it = chains.find(type);
      if ( chains.end() != it )
        vcb = it->second;

      dprint("ChainsSet::get_chain('%s') -> %p\n", type, vcb.get());
      return vcb;
    }
};

//=============================================================================
//
//=============================================================================
/*!
    \class AtomBase
    \brief Interface for templated class Atom
    \ingroup Val
    
 Class AtomBase is a type of interface class, allows to execute atomic
 validation checks for specific type. AtomBase is used in class
 Chain for storing pointers to actual checks in vector. Actual check
 is a template class Atom, see below. AtomBase serves as a layer between
 code which is called for each particular type. Let's consider that we need
 to have two checks to be stored in chain:
 \code
 Class ValidatorSyntax
 {
   public:
     void do_check(const db_ColumnRef& c)
 };

 Class ValidatorLogic
 {
   public:
     void do_check(const db_ColumnRef& c)
 };
 \endcode
 so we can not have a generic algorithm to apply these checks as we have
 different objects-validators
 we can not add:
 Chain->add(ValidatorSyntax(), &ValidatorSyntax::do_check);
 Chain->add(ValidatorLogic(), &ValidatorLogic::do_check);
 but we can hide object and method in parameters and pass only argument to those
 So AtomBase introduces a way to pass object in generic way, while class Atom 
 hides object and its method to run that check
*/
template <typename T>
class AtomBase
{
  public:
    typedef typename boost::shared_ptr<AtomBase> Ptr;
    virtual ~AtomBase() {};
    virtual void do_check(const T&) const {};
    //virtual void do_check(const grt::Ref<T>&) const {};
};

//=============================================================================
//
//=============================================================================
/*! \class Atom 
    \brief Wrapper to forward atomic check to a real validator 

 Class Atom hides object and method to allow passing only checked parameter.
 For detailed explanation see comments for AtomBase
 */
template <typename Validator, typename T>
class Atom : public AtomBase<T>
{
  public:
    Atom(Validator* v, void (Validator::*pred)(const T&) const)
        : AtomBase<T>()
        , val_(v)
        , pred_(pred)
    {}

    void do_check(const T& obj) const
    {
      dprint("\t\t\tAtom<%s>::do_check(%s)\n", T::RefType::static_class_name().c_str(), obj.class_name().c_str());
      if ( val_ && pred_ )
      {
        (val_->*pred_)(obj);
      }
    }
  private:
    Validator *val_;
    void (Validator::*pred_)(const T&) const; 
};

/*!
    \fn atom
    \ingroup Val
     Wrapper to ease creation of Atom classes. It automaticaly deduces 
     parameters' types and instantiates apropriate Atom class
 */
template <typename Validator, typename T>
typename AtomBase<T>::Ptr atom(Validator* v, void (Validator::*p)(const T&) const)
{
  dprint("\t\t\tAtomBase<%s>::Ptr atom(Validator *v, pred(const &%s)\n"
            , T::RefType::static_class_name().c_str() 
            , T::RefType::static_class_name().c_str()
        );
  return typename AtomBase<T>::Ptr(new Atom<Validator, T>(v, p));
}

//=============================================================================
//
//=============================================================================
/*! \class Chain
    \brief Stores validations checks for a type
    \ingroup Val

 Class Chain stores validations via add_checks. 
 To run validation on specific type we need only to write:
 Chain<db_Table>::validate(table, cs);
 Where cs parameter is of type ChainsSet, and which stores validations for
 particular case: either it is syntax validation, of logic validation
*/
template <typename T>
class Chain : public ChainBase
{
  public:
    //typedef boost::shared_ptr<Chain<T> > Ptr;
    typedef typename T::RefType  Type;
    typedef Chain<T>* Ptr;

    //! Method chain hides details of getting Chain for a type
    //! It can be used in the following way:
    //! Chain<db_Index>::chain(cs);
    //! Where cs is ChainsSet which holds validation sets
    static Ptr chain(ChainsSet* chains)
    {
      ChainBase::Ptr chainBase = chains->get_chain(Type::static_class_name().c_str());
      Ptr chain = (Chain<T>*)(chainBase.get());
      if ( !chain )
      {
        ChainBase::Ptr ch(chain = new Chain);
        chains->set_chain(Type::static_class_name().c_str(), ch);
      }
      
      return chain;
    }
    
    // Method validate runs concrete validation checks on an object
    //static void validate(const grt::Ref<T> &object, ChainsSet *chains)
    static void validate(const T &object, ChainsSet *chains)
    {
      ChainBase::Ptr chainBase = chains->get_chain(Type::static_class_name().c_str());
      dprint("\tChain<%s>::validate(%s): got chain %p\n", Type::static_class_name().c_str(), object.class_name().c_str(), chainBase.get());
      Chain<T>* chain_ = (Chain<T>*)(chainBase.get());

      dprint("\t(%p)Chain<%s>::validate(%s)\n", chain_, Type::static_class_name().c_str(), object.class_name().c_str());
      if ( chain_ )
        chain_->do_checks(object);
    }

    void add_check(typename AtomBase<T>::Ptr atom_)
    {
      dprint("\t(%p)Chain<%s>::add_check(AtomBase<%s>)\n", this, Type::static_class_name().c_str(), T::RefType::static_class_name().c_str());
      chain_.push_back(atom_);
    }    
    
    void clear()
    {
      chain_.clear();
    }
  private:
    //void do_checks(const grt::Ref<T>& object)
    void do_checks(const T& object)
    {
      dprint("\t\t(%p)Chain<%s>::do_checks(%s) chain_ -> %p\n", this, Type::static_class_name().c_str(), object.class_name().c_str(), &chain_);

      typename Atoms::const_iterator       check      = chain_.begin();
      const typename Atoms::const_iterator last_check = chain_.end();
      for (; last_check != check; ++check )
      {
        if ( *check )
          (*check)->do_check(object);      
      }
    }

    typedef std::vector<typename AtomBase<T>::Ptr> Atoms;
    Atoms chain_;
};

} //end of namespace val

//-----------------------------------------------------------------------------
template <typename List, typename Obj, typename Pred>
inline void forEach(const List &l, Obj obj, Pred p)
{
  std::for_each(l.begin(), l.end(), boost::bind(p, obj, _1));
}

//-----------------------------------------------------------------------------
template <typename List, typename Pred>
inline void forEach(const List &l, Pred p)
{
  std::for_each(l.begin(), l.end(), p);
}

//-----------------------------------------------------------------------------
inline grt::StringRef defaultExtractName(const GrtNamedObjectRef& obj)
{
  return  obj->name();
}

//-----------------------------------------------------------------------------
template <typename List, typename ExtractName>
bool is_name_in_list(const List &l, const grt::StringRef& name, const ExtractName extName)
{
  bool found = false; 
  if ( l.is_valid() )
  {
          typename List::const_iterator it   = l.begin();
    const typename List::const_iterator last = l.end();
    for ( ; last != it; it++ )
    {
      if ( name == extName(*it) )
      {
        found = true;
        break;
      }
    }
  }
  return found;
}

//=============================================================================
/*! \class ResultsList
    \brief Contains validation errors, warnings 
    \ingroup Val

 Class ResultsList aims to gather errors with their contexts in one place
 in order to navigate to error spot, etc.
*/
//=============================================================================
class ResultsList
{
  public:
    ResultsList(bec::Reporter *rep) : rep_(rep) {}

    void add_error(const std::string description)     //!< adds error to the list
    {
      errors.push_back(description);
      rep_->report_error(description.c_str(),"","");
    }

    void add_error(const char* fmt, ...);     //!< adds error to the list

    void add_warning(const std::string& wrn)
    {
      warnings.push_back(wrn);
      rep_->report_warning(wrn.c_str(),"","");
    }

    //! adds warning to the list
    //! \param fmt format string with printf style formating
    void add_warning(const char* fmt, ...);
    
    //! returns number of errors
    int errors_count() const
    {
      return errors.size();
    };

    //! returns number of warnings
    int warnings_count() const
    {
      return warnings.size();
    };
    
    //! Prints errors/warnings with bec::Reporter
    void print(bec::Reporter& rep)
    {
      std::vector<std::string>::const_iterator     it = errors.begin();
      std::vector<std::string>::const_iterator   last = errors.end();
      for ( ;last != it; ++it )
        rep.report_error("%s", it->c_str());

      it   = warnings.begin();
      last = warnings.end();
      for ( ;last != it; ++it )
        rep.report_warning("%s", it->c_str());
      
      #ifdef VALIDATION_DEBUG
      print();
      #endif
    }
    
    //! Prints errors/warnings to stdout
    void print()
    {
      std::cout << "Results:\n";
      std::vector<std::string>::const_iterator   it = errors.begin();
      std::vector<std::string>::const_iterator last = errors.end();
      for ( ;last != it; ++it )
        std::cout << "        Error: " << it->c_str() << std::endl;

      it = warnings.begin();
      last = warnings.end();
      for ( ;last != it; ++it )
        std::cout << "        Warning: " << it->c_str() << std::endl;
    }
    
  private:
    std::vector<std::string> errors;
    std::vector<std::string> warnings;
    bec::Reporter            *rep_;
};

//-----------------------------------------------------------------------------
inline void ResultsList::add_error(const char* fmt, ...)
{
  enum {SIZE=512};
  char temp[SIZE];
  va_list args;

  va_start(args, fmt);
  vsnprintf(temp, SIZE, fmt, args);
  va_end(args);

  add_error(std::string(temp));
}

//-----------------------------------------------------------------------------
inline void ResultsList::add_warning(const char* fmt, ...)
{
  enum {SIZE=512};
  char temp[SIZE];
  va_list args;

  va_start(args, fmt);
  vsnprintf(temp, SIZE, fmt, args);
  va_end(args);

  add_warning(std::string(temp));
}


typedef std::map<std::string, GrtNamedObjectRef> Names;

class Validators
{
  public:
    Validators(ResultsList *rl, bec::Reporter *rep) 
               : resultsList(rl)
               , reporter(rep) {};

    ResultsList        *resultsList;
    bec::Reporter      *reporter;
  private:
    mutable double          totalItemsToCheck;
    mutable int             passedChecks;
};

#endif

//! @}
