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

#include <fstream>
#include <iostream>
#include <iterator>
#include <boost/program_options.hpp>
#include "parser.h"
#include "ct_transform.h"
#include "cbcs.h"
#include "concepts.h"
#include "distance.h"
#include "bionj_clean.h"

using namespace std;

void transform_ct( string const& filename, ostream & out )
{
  list<file_sequence> sequences;
  parse_ct( filename.c_str(), sequences );
  to_mixed_fasta( sequences, out );
}

void transform_bdb( string const& filename, ostream & out )
{
  list<file_sequence> sequences;
  parse_bdb( filename.c_str(), sequences );
  typedef sequence_data< brace_fold_data< num_nuc<name_data< base_adaptor<file_sequence::iterator> > > > > fold_type;
  to_ct( fold_type( sequences ), out );
}


void detect( string const& filename, profdist::distance_matrix & matrix, vector<string> & names )
{
  list<file_sequence> sequences;

  bool parse_as_marna = false;
  try {
    parse_forester( filename, sequences );
  } catch( runtime_error const& e ) {
    parse_as_marna = ! (sequences.size() >= 2);
  }

  if( parse_as_marna )
    parse_marna( filename.c_str(), sequences );

  CbcDetector detector;
  typedef sequence_data< brace_fold_data< num_sequences<base_adaptor<file_sequence::iterator> > > > fold_type;
  profdist::compute_distance( fold_type( sequences ), matrix, detector );

  typedef name_data<base_adaptor<file_sequence::iterator> > name_front; 
  name_front name_data( sequences ); 
  for( name_front::const_name_iterator it = name_data.name_begin(), e = name_data.name_end(); 
      it != e; ++it)
    names.push_back(string(it->first, it->second ) );
}

void generate_tree( vector<string> const& names, profdist::distance_matrix const& distances, ostream & out )
{
  if( distances.nRows() < 3 )
    throw runtime_error("Bionj needs at least three nodes");

  ProgressSink sink;
  profdist::bionj( distances, names, out, sink );
}

int main( int argc, char **argv )
{
  try {
    namespace po = boost::program_options;
    po::options_description desc("Usage");
    desc.add_options()
      ("help", "produce help message")
      ("output,o", po::value<string>(), "set output file name, otherwise print to stdout")
      ("tsv",  "print matrix as tab separated values")
      ("csv",  "print matrix as comma separated values")
      ("detect",  "detects cbcs in alignments obtained by MARNA or RNAforester, and prints a distance matrix")
      ("transformct",  "transforms ct-files into bracket dot bracket files")
      ("transformbdb",  "transforms bracket dot bracket files into ct-files")
      ("tree",  "creates a tree based on a cbc distance matrix")
      ("tree",  "creates directly a cbc-tree based on alignments obtained by MARNA or RNAforester")
      ;
    po::options_description hidden("Hidden options");
    hidden.add_options()
          ("input-file", po::value< vector<string> >(), "input file")
              ;        
    po::positional_options_description p;
    p.add("input-file", -1);
    
    po::options_description all_opts;
    all_opts.add(desc).add(hidden);

    po::variables_map vm;
    po::store(po::command_line_parser(argc, argv).options(all_opts).positional(p).run(), vm);
    po::notify(vm);    

    if( vm.count("help") ){
      cout << desc << "\n";
      return 0;
    }

    ofstream out;
    if( vm.count("output") )
    {
      out.open(vm["output"].as<string>().c_str());
      if( ! out ) 
        throw runtime_error("Error opening output file");
    }

    if( vm.count("detect") )
    {
      vector<string> const& ref = vm["input-file"].as<vector<string> >();
      for( size_t i = 0, e= ref.size(); i != e; ++i )
      {
        vector<string> names;
        profdist::distance_matrix matrix;
        detect(  ref[i], matrix, names );

        if( vm.count("tsv") )
          profdist::print_distance_matrix_tsv( 
              vm.count("output") 
              ? out
              : cout 
              , matrix
              , names.begin()
              , names.end(), 
              '\t', 
              true 
              );
        else if( vm.count("csv") )
          profdist::print_distance_matrix_tsv(
              vm.count("output") 
              ? out
              : cout 
              , matrix
              , names.begin()
              , names.end()
              , ','
              , true 
              );

        else 
          profdist::print_distance_matrix( 
              vm.count("output") 
              ? out
              : cout 
              , matrix
              , names.begin()
              , names.end()
              , true 
              );

      }
      return 0;
    } 
    if( vm.count("tree") )
    {
      vector<string> const& ref = vm["input-file"].as<vector<string> >();

      for( size_t i = 0, e= ref.size(); i != e; ++i )
      {
        ifstream in( ref[i].c_str() );
        vector<string> names;
        profdist::distance_matrix matrix;

        if(! ( in && profdist::read_distance_matrix( in, matrix, back_insert_iterator<vector<string> >(names) ) ) )
        {
          names.clear();

          detect(  ref[i], matrix, names );
        }

        generate_tree(
            names
            , matrix
            , vm.count("output") 
            ? out
            : cout 
            );
      }
      return 0;
    } 




    if(vm.count("transformct")) 
    {
      vector<string> const& ref = vm["input-file"].as<vector<string> >();
      for( size_t i = 0, e= ref.size(); i != e; ++i )
        transform_ct( 
            ref[i]
            ,  vm.count("output")?out:cout
            );
      return 0;
    }
    if(vm.count("transformbdb")) {
      vector<string> const& ref = vm["input-file"].as<vector<string> >();

      for( size_t i = 0, e= ref.size(); i != e; ++i )
        transform_bdb( 
            ref[i]
            ,  vm.count("output")?out:cout
            );
      return 0;
    }


    cout << desc << "\n";
  }
  catch( runtime_error const& e )  {
    cerr << e.what() << endl;
  }
  catch(  exception const & e ) {
    cerr << e.what() << endl;
  } 
  catch(   ... ) {
    cerr << "unknown exception caught"<< endl;
  }

}
