/***************************************************************************
 *   Copyright (C) 2005 by Andreas Pokorny                                 *
 *   andreas.pokorny@biozentrum.uni-wuerzburg.de                           *
 *                                                                         *
 *   This file is part of profdist and cbcanalyzer                         *
 *                                                                         *
 *   Both profdist and cbcanalyzer are 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 2 of the * 
 *   License, or (at your option) any later version.                       *
 *                                                                         *
 *   Profdist and cbcanalyzer are 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, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#ifndef PROFDIST_DISTANCE_INL_INCLUDED
#define PROFDIST_DISTANCE_INL_INCLUDED
#include <algorithm>
#include <boost/algorithm/string/replace.hpp>
#include "pair_iterator.h"

namespace profdist {
namespace detail {
  template<typename Iterator1, typename Iterator2> 
  bool get_next_brace_pair( 
      pair_iterator<const char, Iterator1, Iterator2> & open, 
      pair_iterator<const char, Iterator1, Iterator2> & close, 
      pair_iterator<const char, Iterator1, Iterator2> & end, 
      std::pair<std::size_t, std::size_t> & positions
      )
  {
    if( open == end || close == end ) 
      return false;

    if( open != close )
    {
      ++open;
      ++positions.first;
    }

    while( *open != '(' && open != end )
    {
      ++open; 
      ++positions.first;
    }

    if( open == end )
      return false;

    positions.second = positions.first;
    close = open;

    std::size_t stack = 0;
    
    while( close != end ) 
    {
      ++close;
      ++positions.second;
      if( *close == ')' )
      {
        if( stack == 0 )
          return  true;
        else --stack;
      }

      if( *close == '(' )
        ++stack;
    }
    return false;
  }
}

template<typename IteratorT, typename Detector>
void compute_distance( sequence_data< brace_fold_data< num_sequences<base_adaptor<IteratorT> > > > const& source, profdist::distance_matrix & matrix, Detector const& detect )
{
  typedef sequence_data< brace_fold_data< num_sequences<base_adaptor<IteratorT> > > > source_type;
  
  std::size_t num_seq = source.get_num_sequences();
  //matrix = profdist::distance_matrix( num_seq, num_seq, 0.0 );
  if(matrix.nRows() != num_seq || matrix.nCols() != num_seq)
  {
    matrix.resize(num_seq, num_seq, 0.0);
  }

  std::size_t i_index = 0;
  for( typename source_type::const_sequence_id i = source.begin(), e = source.end(); 
      i != e; ++i, ++i_index )
  {
    typename source_type::const_sequence_id k = i;
    ++k;
    for(std::size_t k_index = i_index + 1; k != e; ++k, ++k_index )
    {
      std::pair<std::size_t, std::size_t> i_pos(0,0), k_pos(0,0);

      pair_iterator<const char, typename source_type::const_brace_iterator, typename source_type::const_sequence_iterator>
        i_open( source.brace_begin( i ), source.sequence_begin( i ) )
        , i_close( i_open )
        , i_end( source.brace_end( i ), source.sequence_end( i ) ) 
        , k_open( source.brace_begin( k ), source.sequence_begin( k ) )
        , k_close( k_open )
        , k_end( source.brace_end( k ), source.sequence_end( k ) );

      while( i_open != i_end && k_open != k_end )
      {
        bool ret;
        if( i_pos.first < k_pos.first )
          ret = detail::get_next_brace_pair( i_open, i_close, i_end, i_pos );
        else
          ret = detail::get_next_brace_pair( k_open, k_close, k_end, k_pos );

        if( ! ret )
          break;

        if( i_pos == k_pos && 1 == detect( *i_open.second(), *i_close.second(), *k_open.second(), *k_close.second() ) )
        {
          ++matrix( i_index, k_index );
          ++matrix( k_index, i_index );
        }
      }
    }
  }
}

namespace detail {
  inline std::size_t get_size( std::string const&  p ) 
  { return p.length(); }

  template<typename IteratorT>
  inline std::size_t get_size( std::pair<IteratorT, IteratorT> const& p ) 
  { return std::distance( p.first, p.second); }


  template<typename ValueT>
  bool compare_pair_length( ValueT const& l, ValueT const& r) 
  { return get_size( l ) < get_size( r ) ; }

  inline std::string prepare_for_print( std::string const& p)
  { return p; }

  template<typename IteratorT>
  inline std::string prepare_for_print( std::pair<IteratorT, IteratorT> const& p)
  { return std::string( p.first, p.second ); }
}

template<typename IteratorT>
std::ostream& print_distance_matrix( std::ostream& out, profdist::distance_matrix const& matrix, IteratorT begin, IteratorT end, bool FixedPoint = false, bool WellFormated = true  )
{
  std::size_t str_size = 0, f_size = 0;
  if(WellFormated)
  {
    IteratorT biggest = std::max_element( begin, end, &detail::compare_pair_length<typename IteratorT::value_type>);
    str_size = detail::get_size( *biggest );
    
	if( FixedPoint )
    {
        double max_d = *(std::max_element( matrix.begin(), matrix.end() ));
        f_size = std::size_t( std::log10( max_d ) ) + 1 ;
    }
    else 
    {
      out.setf( ios::scientific, ios::floatfield ); 
      out.setf( ios::right, ios::adjustfield ); 
      out.setf( ios::showpoint ); 
      out.precision( 5 ); 
      f_size = 12;
    }
  }

  out << matrix.nRows() << '\n';
  std::size_t i = 0; 
  while(begin != end)
  {
    out << setw(str_size) << boost::replace_all_copy( detail::prepare_for_print( *begin ), " ", "_" );
    for( std::size_t j = 0; j < matrix.nRows(); ++j )
    {
      out << ' '<< setw(f_size) <<  matrix(i,j);
    }
    out << '\n';
    ++begin; 
    ++i;
  }

  return out;
}

template<typename IteratorT>
std::ostream& print_distance_matrix_tsv( std::ostream& out, profdist::distance_matrix const& matrix, IteratorT begin, IteratorT end, char sep = '\t', bool FixedPoint = false )
{
  if( ! FixedPoint )
  {
    out.setf( ios::scientific, ios::floatfield ); 
    out.setf( ios::right, ios::adjustfield ); 
    out.setf( ios::showpoint ); 
    out.precision( 5 ); 
  }
 
  out << matrix.nRows();
  std::size_t i = 0; 
  
  for(IteratorT cp = begin;cp != end; out << sep << detail::prepare_for_print( *cp++ )); 
  
  out << '\n'; 
  while(begin != end)
  {
    out << detail::prepare_for_print( *begin++ );
    for( std::size_t j = 0; j < matrix.nRows(); ++j ) 
    {
      out << sep << matrix(i,j);
    }
    out << '\n';
    ++i;
  }

  return out;
}


template<typename InsertIteratorT>
std::istream& read_distance_matrix( std::istream& in, profdist::distance_matrix & matrix, InsertIteratorT begin )
{
  in >> std::ws;
  std::size_t num;
  if(in >> num) 
  {
    matrix = profdist::distance_matrix( num, num, 0.0 );
    std::size_t i = 0; 
    for( std::string name; i < num && (in >> std::ws >> name >> std::ws); ++i )
    {
      *begin++ = name;
      
      std::size_t k = 0;
      for( double val; k < num && (in >> val);  ++k )  
        matrix(i,k) = val;
    }
  }
  return in;
}

std::istream& read_distance_matrix( std::istream& in, profdist::distance_matrix & matrix)
{
  in >> std::ws;
  std::size_t num;
  if(in >> num) 
  {
    matrix = profdist::distance_matrix( num, num, 0.0 );
    std::size_t i = 0; 
    for( std::string name; i < num && (in >> std::ws >> name >> std::ws); ++i )
    {
      
      std::size_t k = 0;
      for( double val; k < num && (in >> val);  ++k )  
        matrix(i,k) = val;
    }
  }
  return in;
}


}

#endif

