/* Copyright (c) 2018
   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"

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

   private:
      typedef FlipDataSpec BaseSpec;
      std::list<Flip> all_flips;
      TriangNode source_optimal;
   
      friend class ::FlipDataSym<FlipChecker, APCType>;
      friend class ::FlipDataNoSym<FlipChecker, APCType>;

      // methods:
      void _remove_destroyed_flips(const Flip& flip){
         all_flips.remove_if(is_evil(flip));
      }


      void insert_raw_flip(Flip flip){
         all_flips.push_back(flip);
      }


      void check_and_insert_flip(Flip flip){
         TriangNode target = static_cast<FlipDataSpec*>(this)->get_target_triangulation(flip);
         gkz_vector_type current_gkz;
         int isUpflip = static_cast<FlipDataSpec*>(this)->is_lex_upflip(flip, target, current_gkz);
         // Check whether flip satisfies requirements of Flip checker
         if(!this->fc.isValid(source_optimal, target, current_gkz)){
            return;
         }
         static_cast<FlipDataSpec*>(this)->insert_flip(flip, current_gkz, isUpflip);
      }


      void populate(){
         this->upflips.clear();
         this->downflips.clear();
         for(std::list<Flip>::const_iterator flip=all_flips.begin();
               flip != all_flips.end();
               ++flip){
            check_and_insert_flip(*flip);
         }
      }

      void mutate_inner(const Flip& flip){
         this->mutate_core(flip);
         populate();
      }

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


      FlipData_enhanced(
            const APCType& apc_in,
            TriangNode triang,
            const FlipChecker& fc_in
            ):
         FDC(apc_in, triang, fc_in)
         {
            static_cast<FlipDataSpec*>(this)->optimize_source();
            this->_add_new_flips(this->source_triang);
            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
