/*
 *  This file is a part of TiledArray.
 *  Copyright (C) 2013  Virginia Tech
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *  Justus Calvin
 *  Department of Chemistry, Virginia Tech
 *
 *  scal_tsr_expr.h
 *  Apr 1, 2014
 *
 */


#ifndef TILEDARRAY_EXPRESSIONS_SCAL_TSR_EXPR_H__INCLUDED
#define TILEDARRAY_EXPRESSIONS_SCAL_TSR_EXPR_H__INCLUDED

#include <TiledArray/expressions/scal_tsr_engine.h>

namespace TiledArray {
  namespace expressions {

    template <typename Array>
    using ConjTsrExpr = ScalTsrExpr<Array, TiledArray::detail::ComplexConjugate<void> >;

    template <typename Array, typename Scalar>
    using ScalConjTsrExpr = ScalTsrExpr<Array, TiledArray::detail::ComplexConjugate<Scalar> >;

    using TiledArray::detail::conj_op;
    using TiledArray::detail::mult_t;
    using TiledArray::detail::numeric_t;

    template <typename, typename> class ScalTsrExpr;

    template <typename Array, typename Scalar>
    struct ExprTrait<ScalTsrExpr<Array, Scalar> > {
      typedef Array array_type; ///< The \c Array type
      typedef ScalTsrEngine<Array, Scalar> engine_type; ///< Expression engine type
      typedef TiledArray::detail::numeric_t<Array>
          numeric_type; ///< Array base numeric type
      typedef Scalar scalar_type;  ///< Expression scalar type
    };

    /// Expression wrapper for scaled array objects

    /// \tparam Array A `DistArray` type
    /// \tparam Scalar The scaling factor type
    template <typename Array, typename Scalar>
    class ScalTsrExpr : public Expr<ScalTsrExpr<Array, Scalar> > {
    public:
      typedef ScalTsrExpr<Array, Scalar> ScalTsrExpr_; ///< This class type
      typedef Expr<ScalTsrExpr_> Expr_; ///< Expression base type
      typedef typename ExprTrait<ScalTsrExpr_>::array_type array_type; ///< The array type
      typedef typename ExprTrait<ScalTsrExpr_>::engine_type engine_type; ///< Expression engine type
      typedef typename ExprTrait<ScalTsrExpr_>::scalar_type scalar_type; ///< Scalar type

    private:

      const array_type& array_; ///< The array that this expression
      std::string vars_; ///< The tensor variable list
      scalar_type factor_; ///< The scaling factor

      // Not allowed
      ScalTsrExpr_& operator=(ScalTsrExpr_&);

    public:

      // Compiler generated functions
      ScalTsrExpr(const ScalTsrExpr_&) = default;
      ScalTsrExpr(ScalTsrExpr_&&) = default;
      ~ScalTsrExpr() = default;
      ScalTsrExpr_& operator=(const ScalTsrExpr_&) = delete;
      ScalTsrExpr_& operator=(ScalTsrExpr_&&) = delete;

      /// Construct a scaled tensor expression

      /// \param array The array object
      /// \param vars The array annotation variables
      /// \param factor The scaling factor
      ScalTsrExpr(const array_type& array, const std::string& vars,
          const scalar_type factor) :
        Expr_(), array_(array), vars_(vars), factor_(factor)
      { }

      /// Array accessor

      /// \return a const reference to this array
      const array_type& array() const { return array_; }

      /// Tensor variable string accessor

      /// \return A const reference to the variable string for this tensor
      const std::string& vars() const { return vars_; }


      /// Scaling factor accessor

      /// \return The expression scaling factor
      scalar_type factor() const { return factor_; }

    }; // class ScalTsrExpr

    /// Scaled-tensor expression factor

    /// \tparam Array A `DistArray` type
    /// \tparam Scalar A scalar type
    /// \param expr The tensor expression object
    /// \param factor The scaling factor
    /// \return A scaled-tensor expression object
    template <typename Array, typename Scalar,
        typename std::enable_if<
            TiledArray::detail::is_numeric<Scalar>::value
        >::type* = nullptr>
    inline ScalTsrExpr<typename std::remove_const<Array>::type, Scalar>
    operator*(const TsrExpr<Array, true>& expr, const Scalar& factor) {
      return ScalTsrExpr<typename std::remove_const<Array>::type, Scalar>(
          expr.array(), expr.vars(), factor);
    }

    /// Scaled-tensor expression factor

    /// \tparam Array A `DistArray` type
    /// \tparam Scalar A scalar type
    /// \param factor The scaling factor
    /// \param expr The tensor expression object
    /// \return A scaled-tensor expression object
    template <typename Array, typename Scalar,
        typename std::enable_if<
            TiledArray::detail::is_numeric<Scalar>::value
        >::type* = nullptr>
    inline ScalTsrExpr<typename std::remove_const<Array>::type, Scalar>
    operator*(const Scalar& factor, const TsrExpr<Array, true>& expr) {
      return ScalTsrExpr<typename std::remove_const<Array>::type, Scalar>(
          expr.array(), expr.vars(), factor);
    }

    /// Scaled-tensor expression factor

    /// \tparam Array A `DistArray` type
    /// \tparam Scalar1 A scalar type
    /// \tparam Scalar2 A scalar type
    /// \param expr The scaled-tensor expression object
    /// \param factor The scaling factor
    /// \return A scaled-tensor expression object
    template <typename Array, typename Scalar1, typename Scalar2,
        typename std::enable_if<
            TiledArray::detail::is_numeric<Scalar2>::value
        >::type* = nullptr>
    inline ScalTsrExpr<Array, mult_t<Scalar1, Scalar2> >
    operator*(const ScalTsrExpr<Array, Scalar1>& expr, const Scalar2& factor) {
      return ScalTsrExpr<Array, mult_t<Scalar1, Scalar2> >(expr.array(),
          expr.vars(), expr.factor() * factor);
    }

    /// Scaled-tensor expression factor

    /// \tparam Array A `DistArray` type
    /// \tparam Scalar1 A scalar type
    /// \tparam Scalar2 A scalar type
    /// \param factor The scaling factor
    /// \param expr The scaled-tensor expression object
    /// \return A scaled-tensor expression object
    template <typename Array, typename Scalar1, typename Scalar2,
        typename std::enable_if<
            TiledArray::detail::is_numeric<Scalar1>::value
        >::type* = nullptr>
    inline ScalTsrExpr<Array, mult_t<Scalar2, Scalar1> >
    operator*(const Scalar1& factor, const ScalTsrExpr<Array, Scalar2>& expr) {
      return ScalTsrExpr<Array, mult_t<Scalar2, Scalar1> >(expr.array(),
          expr.vars(), expr.factor() * factor);
    }

    /// Negated-tensor expression factor

    /// \tparam Array A `DistArray` type
    /// \param expr The tensor expression object
    /// \return A scaled-tensor expression object
    template <typename Array>
    inline ScalTsrExpr<typename std::remove_const<Array>::type,
        typename ExprTrait<TsrExpr<Array, true> >::numeric_type>
    operator-(const TsrExpr<Array, true>& expr) {
      return ScalTsrExpr<typename std::remove_const<Array>::type,
          typename ExprTrait<TsrExpr<Array, true> >::numeric_type>(
              expr.array(), expr.vars(), -1);
    }

    /// Negated-tensor expression factor

    /// \tparam Array A `DistArray` type
    /// \tparam Scalar A scalar type
    /// \param expr The scaled-tensor expression object
    /// \return A scaled-tensor expression object
    template <typename Array, typename Scalar>
    inline ScalTsrExpr<Array, Scalar>
    operator-(const ScalTsrExpr<Array, Scalar>& expr) {
      return ScalTsrExpr<Array, Scalar>(expr.array(), expr.vars(), -expr.factor());
    }

    /// Conjugated tensor expression factory

    /// \tparam Array A `DistArray` type
    /// \param expr The tensor expression object
    /// \return A conjugated expression object
    template <typename Array>
    inline ConjTsrExpr<typename std::remove_const<Array>::type>
    conj(const TsrExpr<Array, true>& expr) {
      return ConjTsrExpr<typename std::remove_const<Array>::type>(expr.array(),
          expr.vars(), conj_op());
    }

    /// Conjugate-conjugate tensor expression factory

    /// \tparam Array A `DistArray` type
    /// \param expr The tensor expression object
    /// \return A tensor expression object
    template <typename Array>
    inline TsrExpr<const Array, true> conj(const ConjTsrExpr<Array>& expr) {
      return TsrExpr<const Array, true>(expr.array(), expr.vars());
    }

    /// Conjugated-tensor expression factor

    /// \tparam Array A `DistArray` type
    /// \tparam Scalar A scalar type
    /// \param expr The tensor expression object
    /// \return A conjugated expression object
    template <typename Array, typename Scalar>
    inline ScalConjTsrExpr<Array, Scalar> conj(const ScalTsrExpr<Array, Scalar>& expr) {
      return ScalConjTsrExpr<Array, Scalar>(expr.array(), expr.vars(),
          conj_op(TiledArray::detail::conj(expr.factor())));
    }

    /// Conjugate-conjugate tensor expression factory

    /// \tparam Array A `DistArray` type
    /// \tparam Scalar A scalar type
    /// \param expr The scaled conjugate tensor expression object
    /// \return A conjugated expression object
    template <typename Array, typename Scalar>
    inline ScalTsrExpr<Array, Scalar>
    conj(const ScalConjTsrExpr<Array, Scalar>& expr) {
      return ScalTsrExpr<Array, Scalar>(expr.array(), expr.vars(),
          TiledArray::detail::conj(expr.factor().factor()));
    }

    /// Scaled-tensor expression factor

    /// \tparam Array A `DistArray` type
    /// \tparam Scalar A scalar type
    /// \param expr The tensor expression object
    /// \param factor The scaling factor
    /// \return A scaled-tensor expression object
    template <typename Array, typename Scalar,
        typename std::enable_if<
            TiledArray::detail::is_numeric<Scalar>::value
        >::type* = nullptr>
    inline ScalConjTsrExpr<Array, Scalar>
    operator*(const ConjTsrExpr<const Array>& expr, const Scalar& factor) {
      return ScalConjTsrExpr<Array, Scalar>(expr.array(), expr.vars(),
          conj_op(factor));
    }

    /// Scaled-tensor expression factor

    /// \tparam Array A `DistArray` type
    /// \tparam Scalar A scalar type
    /// \param factor The scaling factor
    /// \param expr The tensor expression object
    /// \return A scaled-tensor expression object
    template <typename Array, typename Scalar,
        typename std::enable_if<
            TiledArray::detail::is_numeric<Scalar>::value
        >::type* = nullptr>
    inline ScalConjTsrExpr<Array, Scalar>
    operator*(const Scalar& factor, const ConjTsrExpr<Array>& expr) {
      return ScalConjTsrExpr<Array, Scalar>(expr.array(), expr.vars(),
          conj_op(factor));
    }

    /// Scaled-tensor expression factor

    /// \tparam Array A `DistArray` type
    /// \tparam Scalar A scalar type
    /// \param expr The scaled-tensor expression object
    /// \param factor The scaling factor
    /// \return A scaled-tensor expression object
    template <typename Array, typename Scalar1, typename Scalar2,
        typename std::enable_if<
            TiledArray::detail::is_numeric<Scalar2>::value
        >::type* = nullptr>
    inline ScalConjTsrExpr<Array, mult_t<Scalar1, Scalar2> >
    operator*(const ScalConjTsrExpr<Array, Scalar1>& expr, const Scalar2& factor) {
      return ScalConjTsrExpr<Array, mult_t<Scalar1, Scalar2> >(expr.array(),
          expr.vars(), conj_op(expr.factor().factor() * factor));
    }

    /// Scaled-tensor expression factor

    /// \tparam Array A `DistArray` type
    /// \tparam Scalar A scalar type
    /// \param factor The scaling factor
    /// \param expr The scaled-tensor expression object
    /// \return A scaled-tensor expression object
    template <typename Array, typename Scalar1, typename Scalar2,
        typename std::enable_if<
            TiledArray::detail::is_numeric<Scalar1>::value
        >::type* = nullptr>
    inline ScalConjTsrExpr<Array, mult_t<Scalar2, Scalar1> >
    operator*(const Scalar1& factor, const ScalConjTsrExpr<Array, Scalar2>& expr) {
      return ScalConjTsrExpr<Array, mult_t<Scalar2, Scalar1> >(expr.array(),
          expr.vars(), conj_op(expr.factor().factor() * factor));
    }

    /// Negated-conjugated-tensor expression factor

    /// \tparam Array A `DistArray` type
    /// \param expr The tensor expression object
    /// \return A scaled-tensor expression object
    template <typename Array>
    inline ScalConjTsrExpr<Array, typename ExprTrait<ConjTsrExpr<Array> >::numeric_type>
    operator-(const ConjTsrExpr<Array>& expr) {
      typedef typename ExprTrait<ConjTsrExpr<Array> >::numeric_type
          numeric_type;
      return ScalConjTsrExpr<Array, numeric_type>(expr.array(), expr.vars(),
          conj_op<numeric_type>(-1));
    }

    /// Negated-conjugated-tensor expression factor

    /// \tparam Array A `DistArray` type
    /// \tparam Scalar A scalar type
    /// \param expr The scaled-conjugated-tensor expression object
    /// \return A scaled-tensor expression object
    template <typename Array, typename Scalar>
    inline ScalConjTsrExpr<Array, Scalar>
    operator-(const ScalConjTsrExpr<Array, Scalar>& expr) {
      return ScalConjTsrExpr<Array, Scalar>(expr.array(), expr.vars(),
          conj_op(-expr.factor().factor()));
    }

  }  // namespace expressions
} // namespace TiledArray

#endif // TILEDARRAY_EXPRESSIONS_SCAL_TSR_EXPR_H__INCLUDED
