/*
 *  This file is a part of TiledArray.
 *  Copyright (C) 2015  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
 *
 *  complex.h
 *  Dec 11, 2015
 *
 */

#ifndef TILEDARRAY_SRC_TILEDARRAY_TENSOR_COMPLEX_H__INCLUDED
#define TILEDARRAY_SRC_TILEDARRAY_TENSOR_COMPLEX_H__INCLUDED

#include <TiledArray/config.h>
#include <TiledArray/type_traits.h>

namespace TiledArray {
namespace detail {

/// Wrapper function for `std::conj`

/// This function disables the call to `std::conj` for real values to
/// prevent the result from being converted into a complex value.
/// \tparam R A real scalar type
/// \param r The real scalar
/// \return `r`
template <typename R,
          typename std::enable_if<!is_complex<R>::value>::type* = nullptr>
TILEDARRAY_FORCE_INLINE R conj(const R r) {
  return r;
}

/// Wrapper function for std::conj

/// \tparam R The scalar type
/// \param z The complex scalar
/// \return The complex conjugate of `z`
template <typename R>
TILEDARRAY_FORCE_INLINE std::complex<R> conj(const std::complex<R> z) {
  return std::conj(z);
}

/// Inner product of a real value and a numeric value

/// \tparam L A real scalar type
/// \tparam R A numeric type
/// \return `r`
template <typename L, typename R,
          typename std::enable_if<!is_complex<L>::value>::type* = nullptr>
TILEDARRAY_FORCE_INLINE auto inner_product(const L l, const R r) {
  return l * r;
}

/// Inner product of a complex value and a numeric value

/// \tparam L A complex type
/// \tparam R A numeric type
/// \return `r`
template <typename L, typename R,
          typename std::enable_if<is_complex<L>::value>::type* = nullptr>
TILEDARRAY_FORCE_INLINE auto inner_product(const L l, const R r) {
  return TiledArray::detail::conj(l) * r;
}

/// Wrapper function for `std::norm`

/// This function disables the call to `std::conj` for real values to
/// prevent the result from being converted into a complex value.
/// \tparam R A real scalar type
/// \param r The real scalar
/// \return `r`
template <typename R,
          typename std::enable_if<!is_complex<R>::value>::type* = nullptr>
TILEDARRAY_FORCE_INLINE R norm(const R r) {
  return r * r;
}

/// Compute the norm of a complex number `z`

/// \f[
///   {\rm norm}(z) = zz^* = {\rm Re}(z)^2 + {\rm Im}(z)^2
/// \f]
/// \tparam R The scalar type
/// \param z The complex scalar
/// \return The complex conjugate of `z`
template <typename R>
TILEDARRAY_FORCE_INLINE R norm(const std::complex<R> z) {
  const R real = z.real();
  const R imag = z.imag();
  return real * real + imag * imag;
}

/// ComplexConjugate<S> represents a complex-conjugated value of type \c S.
/// It is used to implement the group generated by negation (multiplication by
/// -1) and complex conjugation operators.
template <typename S>
class ComplexConjugate {
  S factor_;

 public:
  ComplexConjugate(const S factor) : factor_(factor) {}

  TILEDARRAY_FORCE_INLINE S factor() const { return factor_; }

  TILEDARRAY_FORCE_INLINE ComplexConjugate<S> operator-() const {
    return ComplexConjugate<S>(-factor_);
  }

  friend std::ostream& operator<<(std::ostream& os,
                                  const ComplexConjugate<S>& cc) {
    os << "conj()] [" << cc.factor_;
    return os;
  }
};

struct ComplexNegTag {};

/// Specialization of ComplexConjugate<S> for the case of a unit/identity factor
template <>
class ComplexConjugate<void> {
 public:
  inline ComplexConjugate<ComplexNegTag> operator-() const;

  friend std::ostream& operator<<(std::ostream& os,
                                  const ComplexConjugate<void>& cc) {
    os << "conj()";
    return os;
  }
};

/// Specialization of ComplexConjugate<S> for the case of a negative
/// unit/identity factor
template <>
class ComplexConjugate<ComplexNegTag> {
 public:
  inline ComplexConjugate<void> operator-() const;

  friend std::ostream& operator<<(std::ostream& os,
                                  const ComplexConjugate<ComplexNegTag>& cc) {
    os << "conj()] [-1";
    return os;
  }
};

inline ComplexConjugate<void> ComplexConjugate<ComplexNegTag>::operator-()
    const {
  return ComplexConjugate<void>();
}

inline ComplexConjugate<ComplexNegTag> ComplexConjugate<void>::operator-()
    const {
  return ComplexConjugate<ComplexNegTag>();
}

template <typename S>
struct is_numeric<ComplexConjugate<S> > : public std::true_type {};

/// ComplexConjugate operator factory function

/// \tparam S The scalar type
/// \param factor The scaling factor
/// \return A scaling complex conjugate operator
template <typename S>
inline ComplexConjugate<S> conj_op(const S factor) {
  return ComplexConjugate<S>(factor);
}

/// ComplexConjugate operator factory function

/// \return A complex conjugate operator
inline ComplexConjugate<void> conj_op() { return ComplexConjugate<void>(); }

template <typename L, typename R>
TILEDARRAY_FORCE_INLINE auto operator*(const L value,
                                       const ComplexConjugate<R> op) {
  return TiledArray::detail::conj(value) * op.factor();
}

template <typename L>
TILEDARRAY_FORCE_INLINE auto operator*(const L value,
                                       const ComplexConjugate<void>&) {
  return TiledArray::detail::conj(value);
}

template <typename L>
TILEDARRAY_FORCE_INLINE auto operator*(const L value,
                                       const ComplexConjugate<ComplexNegTag>&) {
  return -TiledArray::detail::conj(value);
}

template <typename L, typename R>
TILEDARRAY_FORCE_INLINE auto operator*(const ComplexConjugate<L> op,
                                       const R value) {
  return TiledArray::detail::conj(value) * op.factor();
}

template <typename R>
TILEDARRAY_FORCE_INLINE auto operator*(const ComplexConjugate<void>,
                                       const R value) {
  return TiledArray::detail::conj(value);
}

template <typename R>
TILEDARRAY_FORCE_INLINE auto operator*(const ComplexConjugate<ComplexNegTag>,
                                       const R value) {
  return -TiledArray::detail::conj(value);
}

template <typename L, typename R,
          typename std::enable_if<!std::is_void<R>::value>::type* = nullptr>
TILEDARRAY_FORCE_INLINE L& operator*=(L& value, const ComplexConjugate<R> op) {
  value = TiledArray::detail::conj(value) * op.factor();
  return value;
}

template <typename L>
TILEDARRAY_FORCE_INLINE L& operator*=(L& value, const ComplexConjugate<void>&) {
  value = TiledArray::detail::conj(value);
  return value;
}

template <typename L>
TILEDARRAY_FORCE_INLINE L& operator*=(L& value,
                                      const ComplexConjugate<ComplexNegTag>&) {
  value = -TiledArray::detail::conj(value);
  return value;
}

template <typename T>
inline auto abs(const ComplexConjugate<T>& a) {
  return std::abs(a.factor());
}

inline int abs(const ComplexConjugate<void>& a) { return 1; }

}  // namespace detail
}  // namespace TiledArray

#endif  // TILEDARRAY_SRC_TILEDARRAY_TENSOR_COMPLEX_H__INCLUDED
