// Copyright (c) 2013-2018 Commissariat à l'énergie atomique et aux énergies alternatives (CEA)
// Copyright (c) 2013-2018 Centre national de la recherche scientifique (CNRS)
// Copyright (c) 2018-2019 Simons Foundation
//
// 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 may obtain a copy of the License at
//     https://www.gnu.org/licenses/gpl-3.0.txt
//
// Authors: Olivier Parcollet, Nils Wentzell

#ifndef TRIQS_ARRAYS_CACHE_H
#define TRIQS_ARRAYS_CACHE_H
#include "./array.hpp"
#include <triqs/utility/view_tools.hpp>
namespace triqs {
  namespace arrays {

    // ----------------- the implementation class -----------------------
    template <typename DataType, bool IsConst> class cache_impl {
      protected:
      typedef memory_layout_t<DataType::domain_type::rank> ml_t;
      ml_t ml;
      std::conditional_t<IsConst, const_view_or_type_t<DataType>, view_or_type_t<DataType>> keeper;
      bool need_copy;
      using CacheType         = array<typename DataType::value_type, DataType::domain_type::rank>;
      using exposed_view_type = std::conditional_t<IsConst, typename CacheType::const_view_type, typename CacheType::view_type>;
      struct internal_data {
        CacheType copy;
        exposed_view_type view;
        internal_data(cache_impl const &P, ml_t ml) : copy(CacheType(P.keeper, ml)), view(copy) {
#ifdef TRIQS_ARRAYS_CACHE_COPY_VERBOSE
          std::cerr << " Cache : copy made " << std::endl << " -- TRACE = --" << std::endl << triqs::utility::stack_trace() << std::endl;
#endif
        }
      };
      friend struct internal_data;
      mutable std::shared_ptr<internal_data> _id;
      internal_data &id() const {
        if (!_id) _id = std::make_shared<internal_data>(*this, ml);
        return *_id;
      }

      // avoid compiling the transformation keeper-> exposed_view_type when it does not make sense, e.g. expressions
      exposed_view_type view1(std::true_type) const {
        if (need_copy)
          return id().view;
        else
          return keeper;
      }
      exposed_view_type view1(std::false_type) const { return id().view; }
      exposed_view_type view2() const { return view1(bool_constant<is_amv_value_or_view_class<DataType>::value>()); }

      bool need_copy_dynamic(DataType const &x, std::false_type) const { return false; }
      bool need_copy_dynamic(DataType const &x, std::true_type) const { return (x.indexmap().memory_layout() != ml); }

      void back_update(std::true_type) {}
      void back_update(std::false_type) {
        if (need_copy) keeper = id().view;
      }

      public:
      cache_impl(DataType const &x, ml_t ml_ = ml_t()) : ml(ml_), keeper(x) {
        need_copy = (!is_amv_value_or_view_class<DataType>::value) || need_copy_dynamic(x, is_amv_value_or_view_class<DataType>())
           || (!has_contiguous_data(x));
      }

      ~cache_impl() { back_update(bool_constant<IsConst>()); }

      cache_impl(cache_impl &&)      = default;
      cache_impl(cache_impl const &) = delete;
      cache_impl &operator=(cache_impl const &) = delete;
      cache_impl &operator=(cache_impl &&) = delete;

      void update() {
        if (need_copy && _id) id().view = keeper;
      }

      exposed_view_type view() const { return view2(); }
      operator exposed_view_type() const { return view2(); }
    };

    // ---------------- Users class and factories ------------------

    template <typename DataType> using cache       = cache_impl<DataType, false>;
    template <typename DataType> using const_cache = cache_impl<DataType, true>;

    template <typename D>
    const_cache<D> make_const_cache(D const &x, memory_layout_t<D::domain_type::rank> ml = memory_layout_t<D::domain_type::rank>{}) {
      return {x, ml};
    }

    template <typename D> cache<D> make_cache(D const &x, memory_layout_t<D::domain_type::rank> ml = memory_layout_t<D::domain_type::rank>{}) {
      return {x, ml};
    }
  } // namespace arrays
} // namespace triqs
#endif
