// $Id: BasePairNeighbours.h 1369 2024-11-29 14:20:00Z ge $
/// \file BasePairNeighbours.h
/// \brief contains the BasePairNeighbours class
///
/// $Revision: 1369 $
/// \author Gerald Weber

#ifndef GBC_EXP_BASEPAIRNEIGHBOURS_H
#define GBC_EXP_BASEPAIRNEIGHBOURS_H "$Id: BasePairNeighbours.h 1369 2024-11-29 14:20:00Z ge $"

#include "BasePair.h"
#include "StrandPair.h"
#include "NucleotideSequence.h"
#include <algorithm>
#include "RegexPattern.h"

namespace gbc {

template<class _InternalTp=char>
/// \brief Takes two objects of type BasePair and forms the stacked Neighbours.
///
/// The first template argument _InternalTp is passed as template argument
/// to the BasePair<_InternalTp> class.
/// The second template argument allows passing an object that may contain
/// parameters associated to this object.
class BasePairNeighbours: public std::pair<BasePair<_InternalTp>,BasePair<_InternalTp> >
  {
  public:
    typedef BasePair<_InternalTp>                  internal_type;   ///< type of _InternalTp
    typedef std::pair<internal_type,internal_type> pair_type;
    typedef std::pair<typename internal_type::internal_pair_type,typename internal_type::internal_pair_type> internal_pair_type;

    const static char separation_char='_'; ///< Separator string for the string representation.

    typename internal_type::SymmetryActionsType Symmetry_action;    ///< Flags which symmetry action will be performed

  BasePairNeighbours(void) {};

  template<class _InternalTp2>
  /// \brief Constructs BasePairNeighbours from two objects of type BasePair.
  BasePairNeighbours(const BasePair<_InternalTp2>& fst, const BasePair<_InternalTp2>& scnd)
    : pair_type(fst,scnd), Symmetry_action(internal_type::default_symmetry_action) {}

  BasePairNeighbours(char fst, char scnd)
   : pair_type(internal_type(fst),internal_type(scnd)), Symmetry_action(internal_type::default_symmetry_action) {}

  BasePairNeighbours(std::string bpname1, std::string bpname2)
   : pair_type(internal_type(bpname1),internal_type(bpname2)), Symmetry_action(internal_type::default_symmetry_action) {}

  BasePairNeighbours(std::string bpname)
   : pair_type(internal_type(bpn2bp1(bpname)),internal_type(bpn2bp2(bpname))), Symmetry_action(internal_type::default_symmetry_action) {}

  template<class _InternalTp2>
  /// \brief Assigns a BasePair to both positions of the BasePairNeighbours.
  ///
  /// This assigns a BasePair to both positions of BasePairNeighbours.
  BasePairNeighbours& operator=(const BasePair<_InternalTp2>& bp)
    {
    this->first=bp;
    this->second=bp;
    return *this;
    }

  /// \brief Swaps the two base pairs stack-wise.
  ///
  /// A base pair of type "AT_CG" would become "CG_AT".
  void stack_swap(void)
    {
    std::swap(this->first,this->second);
    }

  /// \brief Swaps both base pairs axis-wise.
  ///
  /// A base pair of type "AT_CG" would become "TA_GC", by applying
  /// the method BasePair::swap to each base pair.
  void axis_swap(void)
    {
    this->first.swap();
    this->second.swap();
    }

  inline bool is_hybrid(void) const
    {
    auto s11=this->first.first.sugar(), s12=this->first.second.sugar(), s21=this->second.first.sugar(), s22=this->second.second.sugar();
    return (s11 != s12) or (s11 != s21) or (s11 != s22) or (s12 != s21) or (s12 != s22) or (s21 != s22);
    }
    
  /// \brief Swaps the two base pairs stack-wise.
  ///
  /// A base pair of type "AT_CG" would become "CG_AT".
  /// \attentio This inverts the direction 3'->5' to 5'->3'
  friend inline BasePairNeighbours stack_swap(const BasePairNeighbours &nb)
    {
    return BasePairNeighbours(nb.second,nb.first);
    }

  /// \brief Swaps both base pairs axis-wise.
  ///
  /// A base pair of type "AT_CG" would become "TA_GC", by applying
  /// the method BasePair::swap to each base pair.
  friend inline BasePairNeighbours axis_swap(const BasePairNeighbours &nb)
    {
    BasePairNeighbours res=nb;
    res.first.swap(); res.second.swap();
    return res;
    }

  /// \brief Swaps both base pairs axis-wise & stack-wise.
  ///
  /// A base pair of type "AT_CG" would become "GC_TA", this preserves the 5'->3' direction
  friend inline BasePairNeighbours stack_axis_swap(const BasePairNeighbours &nb)
    {
    BasePairNeighbours res=nb;
    res.axis_swap();
    res.stack_swap();
    return res;
    }

  /// \brief Converts this BasePairNeighbours to it's cheapest symmetry
  ///
  /// We apply the symmetry operations of axis swapping, 
  /// stack swapping and both axis and stack swapping.
  /// Then we retain the one that yields the lowest internal
  /// representation value using the operator<.
  ///
  /// \attention if Respect35direction is true, then the only symmetry operation attempted
  /// is axis_swap followed by stack_swap.
  inline bool reduce_to_smallest_symmetry(void)
    {
    bool reduced=false;
    if (Symmetry_action == internal_type::simplify_symmetry)
      {
      BasePairNeighbours axst=*this;
      axst.axis_swap(); axst.stack_swap();
      if (axst < *this) {*this=axst; reduced=true;}
      }
    else
      {
      BasePairNeighbours ax=*this, st=*this, axst=*this;
      ax.axis_swap(); st.stack_swap();
      axst.axis_swap(); axst.stack_swap();
      if (ax < *this)   {*this=ax;   reduced=true;}
      if (st < *this)   {*this=st;   reduced=true;}
      if (axst < *this) {*this=axst; reduced=true;}
      }
    return reduced;
    }

  /// \brief Checks for symmetries along the BasePair axis.
  ///
  /// In regard to the hydrogen bonds AT/CG is symmetric to TA/GC.
  friend inline bool axis_symmetry(const BasePairNeighbours &nb1, const BasePairNeighbours &nb2)
    {
    return   (nb1.first.first  == nb2.first.second)
          && (nb1.first.second == nb2.first.first)
          && (nb1.second.first == nb2.second.second)
          && (nb1.second.second== nb2.second.first);
    }

  /// \brief Checks for symmetries by swaping the BasePairs.
  ///
  /// In regard to the base-pair stacking AT/CG is symmetric to CG/AT.
  friend inline bool stack_symmetry(const BasePairNeighbours &nb1, const BasePairNeighbours &nb2)
    {
    return   (nb1.first  == nb2.second)
          && (nb1.second == nb2.first);
    }

  /// \brief Checks for symmetries by swaping the BasePairs and each around the axis.
  ///
  /// In regard to the base-pair stacking and hydrogen bonds AT/CG is symmetric to GC/TA.
  friend inline bool axis_stack_symmetry(const BasePairNeighbours &nb1, const BasePairNeighbours &nb2)
    {
    return   (nb1.first.first  == nb2.second.second)
          && (nb1.first.second == nb2.second.first)
          && (nb1.second.first == nb2.first.second)
          && (nb1.second.second== nb2.first.first);
    }

  /// \brief Converts the name of two base pairs into the equivalent name of base pair neighbours.
  ///
  /// For example the base pairs of name "AT" and "CG" would return "AT_CG".
  inline static std::string bp2bpn(std::string bp1, std::string bp2)
    {return bp1+separation_char+bp2;}

  /// \brief Returns the name of the first base pair from the base pair neighbours name.
  ///
  /// For example the base pairs of name "AT_CG" would return "AT".
  inline static std::string bpn2bp1(std::string bpn)
    {
    const boost::regex pattern(BASEPAIRNEIGHBOURS_BPN2BP1_PATTERN);
    boost::smatch found;
    if (boost::regex_search(bpn,found,pattern)) return found[1];
    else                                        return std::string();
    }

  /// \brief Returns the name of the first base pair from the base pair neighbours name.
  ///
  /// For example the base pairs of name "AT_CG" would return "AT".
  inline static std::string bpn2bp2(std::string bpn)
    {
    const boost::regex pattern(BASEPAIRNEIGHBOURS_BPN2BP2_PATTERN);
    boost::smatch found;
    if (boost::regex_search(bpn,found,pattern)) return found[2];
    else                                        return std::string();
    }
    
  inline std::string nucleosides_string(void) const
    {
    std::string res=bp2bpn(this->first.nucleosides_string(),this->second.nucleosides_string());
    return res;
    }

  inline std::string nucleobases_string(void) const
    {
    std::string res=bp2bpn(this->first.nucleobases_string(),this->second.nucleobases_string());
    return res;
    }

  /// \brief Produces a string which describes the BasePairNeighbours.
  ///
  /// This method makes use of the separation_char which by default is set to a 
  /// "_" string, examples would be "AT_CG" or "AT_TA".
  inline std::string formatted_string(void) const
    {
    std::string res;
    if (is_hybrid()) res=this->nucleosides_string();
    else             res=this->nucleobases_string();
    return res;
    }

  /// \brief Produces a string which describes the BasePairNeighbours.
  ///
  /// This method makes use of the separation_char which by default is set to a 
  /// "_" string, examples would be "AT_CG" or "AT_TA".
  inline operator std::string(void) const
    {
    return formatted_string();
    }

  inline operator internal_pair_type(void) const
    {
    typename internal_type::internal_pair_type fst=this->first, snd=this->second;
    return internal_pair_type(fst,snd);
    }
    
  inline void main_strand(NucleotideSequence<_InternalTp>& seq) const
    {
    seq.window_size=2;
    seq << this->first.first << this->second.first;
    }

    
  inline void main_strand(StrandPair<_InternalTp>& sp) const
    {
    sp.assign(this->first.first,this->second.first,StrandPair<_InternalTp>::dir53,StrandPair<_InternalTp>::origin_main);
    }

      
   //Returns the sequence in 5'->3 direction
   inline void opposite_strand(NucleotideSequence<_InternalTp>& seq) const
    {
    seq.window_size=2;
    seq << this->second.second << this->first.second;//Reversed to be in 5'->3' direction
    }
    
    //Returns the sequence in original 3'->5 direction, this info is stored in StrandPair
   inline void opposite_strand(StrandPair<_InternalTp>& sp) const
    {
    sp.assign(this->first.second,this->second.second,StrandPair<_InternalTp>::dir35,StrandPair<_InternalTp>::origin_opposite);//Reversed to be in 5'->3' direction
    }
    
   template<class _SequenceTp>
   inline void strands(_SequenceTp& main, _SequenceTp& opp) const
    {
    main_strand(main);
    opposite_strand(opp);
    }


  friend inline BasePairNeighbours representation(internal_pair_type pr)
    {
    internal_type B1=representation(pr.first), B2=representation(pr.second);
    return BasePairNeighbours(B1,B2);
    }

  /// \brief Extractor for printing BasePairNeighbours symbols
  ///
  /// This method first converts the BasePair to its string representation.
  friend inline std::ostream& operator<<(std::ostream &out,const BasePairNeighbours &bp)
    { 
    out << bp.formatted_string();
    return out;
    }

  };//ends class

  /// \brief Swaps both base pairs axis-wise & stack-wise.
  ///
  /// A base pair of type "AT_CG" would become "GC_TA", this preserves the 5'->3' direction
  inline std::string stack_axis_swap_string(std::string snb)
    {
    BasePairNeighbours<> res(snb);
    res.axis_swap();
    res.stack_swap();
    return (std::string)res;
    }

};//ends namespace
#endif
