// Copyright (c) 2013-2020, SIB - Swiss Institute of Bioinformatics and
//                          Biozentrum - University of Basel
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


#include <promod3/core/eigen_types.hh>
#include <promod3/core/graph_minimizer.hh>
#define BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>

#include <ost/base.hh>
#include <vector>
#include <stdlib.h> 

BOOST_AUTO_TEST_SUITE( core );

using namespace promod3::core;


GraphMinimizerPtr GenerateTestGraph() {

  // setup a simple graph that can naively be enumerated

  std::srand(0);
  GraphMinimizerPtr graph(new GraphMinimizer);  
  std::vector<int> num_solutions;

  num_solutions.push_back(3);
  num_solutions.push_back(5);
  num_solutions.push_back(2);
  num_solutions.push_back(8);
  num_solutions.push_back(3);
  num_solutions.push_back(5);

  std::vector<std::pair<int, int> > edges;
  edges.push_back(std::make_pair(0,1));
  edges.push_back(std::make_pair(0,3));
  edges.push_back(std::make_pair(0,4));
  edges.push_back(std::make_pair(0,5));
  edges.push_back(std::make_pair(1,2));
  edges.push_back(std::make_pair(2,3));
  edges.push_back(std::make_pair(2,4));
  edges.push_back(std::make_pair(3,1));
  edges.push_back(std::make_pair(4,5));
  edges.push_back(std::make_pair(5,1));


  for(uint i = 0; i < num_solutions.size(); ++i) {

    std::vector<Real> self_energies;
    for(int j = 0; j < num_solutions[i]; ++j) {
      self_energies.push_back(static_cast<Real>(std::rand()) / RAND_MAX);
    }

    graph->AddNode(self_energies);
  }

  for(uint i = 0; i < edges.size(); ++i) {
    EMatXX emat = EMatXX::Random(num_solutions[edges[i].first],
                                 num_solutions[edges[i].second]);
    graph->AddEdge(edges[i].first, edges[i].second, emat);
  }

  return graph;
}


BOOST_AUTO_TEST_CASE(test_graph_minimizer) {

  // simple test to at least run all solving algorithms once

  GraphMinimizerPtr graph = GenerateTestGraph();

  std::pair<std::vector<int>, Real> naive_solution = graph->NaiveSolve();
  graph->Reset();

  std::pair<std::vector<int>, Real> tree_solution = graph->TreeSolve();
  graph->Reset();

  std::pair<std::vector<std::vector<int> >, std::vector<Real> > astar_solution = 
  graph->AStarSolve(10.0);
  graph->Reset();

  std::pair<std::vector<std::vector<int> >, std::vector<Real> > mc_solution = 
  graph->MCSolve();

  // the solutions and coresponding energies in naive, tree and astar must be 
  // the same

  BOOST_CHECK(naive_solution.first == tree_solution.first);
  BOOST_CHECK(naive_solution.first == astar_solution.first[0]);

  BOOST_CHECK_CLOSE(naive_solution.second, tree_solution.second, Real(1e-3));
  BOOST_CHECK_CLOSE(naive_solution.second, astar_solution.second[0], Real(1e-3));

  // check, whether the energies in the astar solution are sorted

  for(uint i = 1; i < astar_solution.second.size(); ++i) {
    BOOST_CHECK(astar_solution.second[i] >= astar_solution.second[i-1]);
  }

  // check, whether the energies in the mc solution are sorted

  for(uint i = 1; i < mc_solution.second.size(); ++i) {
    BOOST_CHECK(mc_solution.second[i] >= mc_solution.second[i-1]);
  }
}

BOOST_AUTO_TEST_SUITE_END();
