/* Copyright (c) 2018 -- 2022
   Charles Jordan (Hokkaido University, Sapporo, Japan),
   Michael Joswig, Lars Kastner (Technische Universitaet Berlin, Germany)

   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, or (at your option) any
   later version: http://www.gnu.org/licenses/gpl.txt.

   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.
-----------------------------------------------------------------------------
*/

#ifndef FLIPDATA_BASE_HH
#define FLIPDATA_BASE_HH

#include "FlipData_core.hh"

struct is_evil {
   const Flip& flip;
   is_evil(const Flip& in) : flip(in){}
   bool operator() (const Flip& f) { return !(f.first * flip.first).is_empty(); }
};


template<typename FlipChecker, typename APCType, typename FlipDataSpec>
class FlipData_enhanced : public FlipData_core<FlipChecker, typename APCType::pmFlipContainer> {
   
   using pmFlipContainer = typename APCType::pmFlipContainer;
   typedef FlipData_core<FlipChecker, pmFlipContainer> FDC;
   friend class FlipData_core<FlipChecker, pmFlipContainer>;

   private:

      using OwnType = FlipData_enhanced<FlipChecker, APCType, FlipDataSpec>;
      friend void core_add_new_flips<OwnType>(OwnType&, const SimplicialComplex&, const bool);

      typedef FlipDataSpec BaseSpec;
      std::unordered_map<Flip, FlipInfo> all_flips;
      TriangNode source_optimal;
      const APCType& apc;
   
      friend class ::FlipDataSym<FlipChecker, APCType>;
      friend class ::FlipDataNoSym<FlipChecker, APCType>;

      // methods:
      void _remove_destroyed_flips(const Flip& flip){
         // // From c++20
         // std::erase_if(all_flips, is_evil(flip));
         //
         auto it = all_flips.begin();
         auto check = is_evil(flip);
         while(it != all_flips.end())
         {
            if(check(it->first))
            {
               all_flips.erase(it++);
            }
            else
               ++it;
         }
      }


      void insert_raw_flip(Flip flip){
         FlipInfo fi;
         fi.isValid = true;
         all_flips[flip] = fi;
      }


      void mark_central_flips(){
         auto it = all_flips.begin();
         while(it != all_flips.end()){
            if(it->second.isValid){
               it->second.isValid = fixesFirstGKZCoord<typename FlipChecker::checkFine>(it->second.gkz);
            }
            it++;
         }
      }


      void mark_regular_flips(){
         auto it = all_flips.begin();
         while(it != all_flips.end()){
            if(it->second.isValid){
               if constexpr(FlipChecker::checkFine::value){
                  // We only want to check flips preserving fullness for
                  // regularity, but for the regularity check we still need all
                  // full flips.
                  if(it->second.isFine){
                     it->second.isValid = this->fc.isValid(this->source_triang, it->second.gkz, all_flips, it->first);
                  }
               } else {
                  it->second.isValid = this->fc.isValid(this->source_triang, it->second.gkz, all_flips, it->first);
               }
            }
            it++;
         }
      }


      void mark_fine_flips() {
         auto it = all_flips.begin();
         while(it != all_flips.end()){
            if(it->second.isValid){
               if constexpr(!FlipChecker::checkRegular::value){
                  it->second.isValid = isFine(it->second.target);
               } else {
                  it->second.isFine = isFine(it->second.target);
               }
            }
            it++;
         }
      }


      void record_valid_flips() {
         auto it = all_flips.begin();
         while(it != all_flips.end()){
            if((it->second).isValid){
               if constexpr(!FlipChecker::checkRegular::value){
                     const FlipInfo& fi(it->second);
                     static_cast<FlipDataSpec*>(this)->insert_flip(it->first, fi.gkz, fi.isUpflip);
               } else {
                  if constexpr(FlipChecker::checkFine::value){
                     if(it->second.isFine){
                        const FlipInfo& fi(it->second);
                        static_cast<FlipDataSpec*>(this)->insert_flip(it->first, fi.gkz, fi.isUpflip);
                     }
                  } else {
                     const FlipInfo& fi(it->second);
                     static_cast<FlipDataSpec*>(this)->insert_flip(it->first, fi.gkz, fi.isUpflip);
                  }
               }
            }
            it++;
         }
      }


      void populate(){
         this->upflips.clear();
         this->downflips.clear();
         auto it = all_flips.begin();
         while(it != all_flips.end()){
            auto& fi = it->second;
            auto& flip = it->first;
            fi.target = static_cast<FlipDataSpec*>(this)->get_target_triangulation(flip);
            fi.isUpflip = static_cast<FlipDataSpec*>(this)->is_lex_upflip(flip, fi.target, fi.gkz);
            it++;
         }
         if constexpr(FlipChecker::checkCentral::value){
            mark_central_flips();
         }
         if constexpr(FlipChecker::checkFine::value){
            mark_fine_flips();
         }
         if constexpr(FlipChecker::checkRegular::value){
            mark_regular_flips();
         }
         record_valid_flips();
      }


      void mutate_inner(const Flip& flip){
         _remove_destroyed_flips(flip);
         // Some of the old flips might still be marked as invalid, but they
         // could have become valid now. They need to be rechecked.
         auto it = all_flips.begin();
         while(it != all_flips.end()){
            it->second.isValid = true;
            it++;
         }
         // Now add new flips.
         core_add_new_flips(*this, flip.second, false);
         populate();
      }

      // Mutation constructor, should not be public
      FlipData_enhanced(
            const APCType& apc_in,
            TriangNode triang,
            const pmFlipContainer& uf,
            const pmFlipContainer& df,
            const std::unordered_map<Flip, FlipInfo>& af,
            const FlipChecker& fc_in
            ): FDC(triang, uf, df, fc_in, apc_in.get_chiro()),
         apc(apc_in), all_flips(af)
         {
            static_cast<FlipDataSpec*>(this)->optimize_source();
         }


      FlipData_enhanced(
            const APCType& apc_in,
            TriangNode triang,
            const FlipChecker& fc_in
            ):
         FDC(triang, fc_in, apc_in.get_chiro()), apc(apc_in)
         {
            static_cast<FlipDataSpec*>(this)->optimize_source();
            core_add_new_flips(*this, triang, false);
            populate();
         }


   public:

      void mutate(const Flip& flip){
         this->source_triang = TriangNode(0, this->source_triang, flip);
         static_cast<FlipDataSpec*>(this)->optimize_source();
         mutate_inner(flip);
      }

      FlipDataSpec mutate(const Flip& flip, const TriangNode& target){
         FlipDataSpec result(this->apc, target, this->upflips, this->downflips, all_flips, this->fc);
         result.mutate_inner(flip);
         return result;
      }


};



#endif
