#ifndef VIENNACL_META_TAGOF_HPP_
#define VIENNACL_META_TAGOF_HPP_

/* =========================================================================
   Copyright (c) 2010-2014, Institute for Microelectronics,
                            Institute for Analysis and Scientific Computing,
                            TU Wien.
   Portions of this software are copyright by UChicago Argonne, LLC.

                            -----------------
                  ViennaCL - The Vienna Computing Library
                            -----------------

   Project Head:    Karl Rupp                   rupp@iue.tuwien.ac.at

   (A list of authors and contributors can be found in the PDF manual)

   License:         MIT (X11), see file LICENSE in the base directory
============================================================================= */


/** @file tag_of.hpp
    @brief Dispatch facility for distinguishing between ublas, STL and ViennaCL types
*/

#include <vector>
#include <map>

#include "viennacl/forwards.h"

#ifdef VIENNACL_WITH_UBLAS
#include <boost/numeric/ublas/matrix_sparse.hpp>
#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/vector.hpp>
#endif

#ifdef VIENNACL_WITH_EIGEN
#include <Eigen/Core>
#include <Eigen/Sparse>
#endif

#ifdef VIENNACL_WITH_MTL4
#include <boost/numeric/mtl/mtl.hpp>
#endif

namespace viennacl
{

  // ----------------------------------------------------
  // TAGS
  //
  /** @brief A tag class for identifying 'unknown' types. */
  struct tag_none     {};
  /** @brief A tag class for identifying types from MTL4. */
  struct tag_mtl4     {};
  /** @brief A tag class for identifying types from Eigen. */
  struct tag_eigen    {};
  /** @brief A tag class for identifying types from uBLAS. */
  struct tag_ublas    {};
  /** @brief A tag class for identifying types from the C++ STL. */
  struct tag_stl      {};
  /** @brief A tag class for identifying types from ViennaCL. */
  struct tag_viennacl {};

  namespace traits
  {
    // ----------------------------------------------------
    // GENERIC BASE
    //
    /** @brief Generic base for wrapping other linear algebra packages
    *
    *  Maps types to tags, e.g. viennacl::vector to tag_viennacl, ublas::vector to tag_ublas
    *  if the matrix type is unknown, tag_none is returned
    *
    *  This is an internal function only, there is no need for a library user of ViennaCL to care about it any further
    *
    * @tparam T   The type to be inspected
    */
    template< typename T, typename Active = void >
    struct tag_of;

    /** \cond */
    template < typename Sequence, typename Active >
    struct tag_of
    {
      typedef viennacl::tag_none  type;
    };

    #ifdef VIENNACL_WITH_MTL4
    // ----------------------------------------------------
    // MTL4
    //
    template <typename ScalarType>
    struct tag_of< mtl::dense_vector<ScalarType> >
    {
      typedef viennacl::tag_mtl4  type;
    };

    template <typename ScalarType>
    struct tag_of< mtl::compressed2D<ScalarType> >
    {
      typedef viennacl::tag_mtl4  type;
    };

    template <typename ScalarType, typename T>
    struct tag_of< mtl::dense2D<ScalarType, T> >
    {
      typedef viennacl::tag_mtl4  type;
    };
    #endif


    #ifdef VIENNACL_WITH_EIGEN
    // ----------------------------------------------------
    // Eigen
    //
    template <>
    struct tag_of< Eigen::VectorXf >
    {
      typedef viennacl::tag_eigen  type;
    };

    template <>
    struct tag_of< Eigen::VectorXd >
    {
      typedef viennacl::tag_eigen  type;
    };

    template <>
    struct tag_of< Eigen::MatrixXf >
    {
      typedef viennacl::tag_eigen  type;
    };

    template <>
    struct tag_of< Eigen::MatrixXd >
    {
      typedef viennacl::tag_eigen  type;
    };

    template <typename ScalarType, int option>
    struct tag_of< Eigen::SparseMatrix<ScalarType, option> >
    {
      typedef viennacl::tag_eigen  type;
    };

    #endif

    #ifdef VIENNACL_WITH_UBLAS
    // ----------------------------------------------------
    // UBLAS
    //
    template< typename T >
    struct tag_of< boost::numeric::ublas::vector<T> >
    {
      typedef viennacl::tag_ublas  type;
    };

    template< typename T >
    struct tag_of< boost::numeric::ublas::matrix<T> >
    {
      typedef viennacl::tag_ublas  type;
    };

    template< typename T1, typename T2 >
    struct tag_of< boost::numeric::ublas::matrix_unary2<T1,T2> >
    {
      typedef viennacl::tag_ublas  type;
    };

    template< typename T1, typename T2 >
    struct tag_of< boost::numeric::ublas::compressed_matrix<T1,T2> >
    {
      typedef viennacl::tag_ublas  type;
    };

    #endif

    // ----------------------------------------------------
    // STL types
    //

    //vector
    template< typename T, typename A >
    struct tag_of< std::vector<T, A> >
    {
      typedef viennacl::tag_stl  type;
    };

    //dense matrix
    template< typename T, typename A >
    struct tag_of< std::vector<std::vector<T, A>, A> >
    {
      typedef viennacl::tag_stl  type;
    };

    //sparse matrix (vector of maps)
    template< typename KEY, typename DATA, typename COMPARE, typename AMAP, typename AVEC>
    struct tag_of< std::vector<std::map<KEY, DATA, COMPARE, AMAP>, AVEC> >
    {
      typedef viennacl::tag_stl  type;
    };


    // ----------------------------------------------------
    // VIENNACL
    //
    template< typename T, unsigned int alignment >
    struct tag_of< viennacl::vector<T, alignment> >
    {
      typedef viennacl::tag_viennacl  type;
    };

    template< typename T, typename F, unsigned int alignment >
    struct tag_of< viennacl::matrix<T, F, alignment> >
    {
      typedef viennacl::tag_viennacl  type;
    };

    template< typename T1, typename T2, typename OP >
    struct tag_of< viennacl::matrix_expression<T1,T2,OP> >
    {
      typedef viennacl::tag_viennacl  type;
    };

    template< typename T >
    struct tag_of< viennacl::matrix_range<T> >
    {
      typedef viennacl::tag_viennacl  type;
    };

    template< typename T, unsigned int I>
    struct tag_of< viennacl::compressed_matrix<T,I> >
    {
      typedef viennacl::tag_viennacl  type;
    };

    template< typename T, unsigned int I>
    struct tag_of< viennacl::coordinate_matrix<T,I> >
    {
      typedef viennacl::tag_viennacl  type;
    };

    template< typename T, unsigned int I>
    struct tag_of< viennacl::ell_matrix<T,I> >
    {
      typedef viennacl::tag_viennacl  type;
    };

    template< typename T, unsigned int I>
    struct tag_of< viennacl::hyb_matrix<T,I> >
    {
      typedef viennacl::tag_viennacl  type;
    };

    template< typename T, unsigned int I>
    struct tag_of< viennacl::circulant_matrix<T,I> >
    {
      typedef viennacl::tag_viennacl  type;
    };

    template< typename T, unsigned int I>
    struct tag_of< viennacl::hankel_matrix<T,I> >
    {
      typedef viennacl::tag_viennacl  type;
    };

    template< typename T, unsigned int I>
    struct tag_of< viennacl::toeplitz_matrix<T,I> >
    {
      typedef viennacl::tag_viennacl  type;
    };

    template< typename T, unsigned int I>
    struct tag_of< viennacl::vandermonde_matrix<T,I> >
    {
      typedef viennacl::tag_viennacl  type;
    };
    /** \endcond */

    // ----------------------------------------------------
  } // end namespace traits


  /** @brief Meta function which checks whether a tag is tag_mtl4
  *
  *  This is an internal function only, there is no need for a library user of ViennaCL to care about it any further
  */
  template <typename Tag>
  struct is_mtl4
  {
     enum { value = false };
  };

  /** \cond */
  template <>
  struct is_mtl4< viennacl::tag_mtl4 >
  {
     enum { value = true };
  };
  /** \endcond */

  /** @brief Meta function which checks whether a tag is tag_eigen
  *
  *  This is an internal function only, there is no need for a library user of ViennaCL to care about it any further
  */
  template <typename Tag>
  struct is_eigen
  {
     enum { value = false };
  };

  /** \cond */
  template <>
  struct is_eigen< viennacl::tag_eigen >
  {
     enum { value = true };
  };
  /** \endcond */


  /** @brief Meta function which checks whether a tag is tag_ublas
  *
  *  This is an internal function only, there is no need for a library user of ViennaCL to care about it any further
  */
  template <typename Tag>
  struct is_ublas
  {
     enum { value = false };
  };

  /** \cond */
  template <>
  struct is_ublas< viennacl::tag_ublas >
  {
     enum { value = true };
  };
  /** \endcond */

  /** @brief Meta function which checks whether a tag is tag_ublas
  *
  *  This is an internal function only, there is no need for a library user of ViennaCL to care about it any further
  */
  template <typename Tag>
  struct is_stl
  {
     enum { value = false };
  };

  /** \cond */
  template <>
  struct is_stl< viennacl::tag_stl >
  {
     enum { value = true };
  };
  /** \endcond */


  /** @brief Meta function which checks whether a tag is tag_viennacl
  *
  *  This is an internal function only, there is no need for a library user of ViennaCL to care about it any further
  */
  template <typename Tag>
  struct is_viennacl
  {
     enum { value = false };
  };

  /** \cond */
  template <>
  struct is_viennacl< viennacl::tag_viennacl >
  {
     enum { value = true };
  };
  /** \endcond */

} // end namespace viennacl

#endif
