/*
 * Copyright (c) 1999 Johnny C. Lam.
 * All rights reserved.
 *
 * Permission to use, copy, modify, distribute and sell this software
 * and its documentation for any purpose is hereby granted without fee,
 * provided that the above copyright notice appear in all copies and
 * that both that copyright notice and this permission notice appear
 * in supporting documentation.  The author makes no representations
 * about the suitability of this software for any purpose.  It is
 * provided "as is" without express or implied warranty.
 */

#include "no_latent_recomb_strategy.h"

#include <algorithm>
#include <limits>
#include "probability.h"
#include "types.h"

enum Direction { LEFT, RIGHT };

/*
 * Assumes [first_mut, last_mut) is sorted range.
 */

template <class BidirectionalIterator>
Path
GenPath(const Haplotype &s, const Haplotype &t,
	PathStrategy::size_type disease,
	BidirectionalIterator first_mut, BidirectionalIterator last_mut,
	Probability *rules, bool verbose)
{
    typedef BidirectionalIterator iterator_type;
    typedef iterator_traits<iterator_type>::difference_type difference_type;
    
    /* Initialize min_path with solely recomb from s to t. */
    Path min_path(s);
    min_path.Add(Path::Edge(t, rules->log_P(s, t, disease)));
    unsigned int min_num_latents = 0;
    log_probability min_path_weight = min_path.Weight();

    /* Find the path of minimum weight from s to t. */
    do {
	if (verbose) {
	    std::cout << "    " << "Index permutation:";
	    for (iterator_type i = first_mut; i != last_mut; ++i)
		std::cout << " " << *i + 1;
	    std::cout << endl;
	}

	Path latent_path(s);	// Path begins with s
	iterator_type f = first_mut;
	Haplotype prev(s);
	unsigned int num_latents = 0;
	log_probability cumulative_weight = 0;

	while (f != last_mut) {
	    Haplotype latent(prev);
	    latent.Mutate(*f, t[*f]);
	    Haplotype::property_t properties = latent.Properties();
	    if (!(properties & Haplotype::DISEASED))
		latent.SetProperties(properties | Haplotype::LATENT);
	    log_probability latent_prob = rules->log_P(prev, latent, disease);
	    latent_path.Add(Path::Edge(latent, latent_prob));
	    if (latent.IsLatent())
		++num_latents;
	    cumulative_weight += latent_prob;
	    if (cumulative_weight >= min_path_weight)
		break;

	    Path path(latent_path);
	    path.Add(Path::Edge(t, rules->log_P(latent, t, disease)));
	    log_probability path_weight = path.Weight();
	    if (verbose &&
		path_weight != std::numeric_limits<log_probability>::max()) {
		std::cout << "\t" << "Test path:" << endl;
		std::cout << "\t" << "        " << s << endl;
		Path::const_iterator pf = path.begin(), pe = path.end();
		++pf;
		while (pf != pe) {
		    std::cout << "\t" << "  ----> " << pf->haplotype
			      << " :\t" << pf->weight << endl;
		    ++pf;
		}
		std::cout << "\t" << "Test path weight (-log(prob)): "
			  << path_weight << endl;
	    }
	    if ((path_weight < min_path_weight) ||
		((path_weight == min_path_weight) &&
		 (num_latents < min_num_latents))) {
		min_path = path;
		min_num_latents = num_latents;
		min_path_weight = path_weight;
		if (verbose) {
		    std::cout << "\t" << "New min path weight: "
			      << min_path_weight << endl;
		}
	    }

	    prev = latent;
	    ++f;
	}
    } while (next_permutation(first_mut, last_mut));

    return min_path;
}

Path
NoLatentRecombStrategy::MakePath(const Haplotype &s, const Haplotype &t,
				 size_type disease, Probability* rules) const
{
    /* IndexList needs to have bidirectional iterators */
    typedef vector<size_type> IndexList;
    unsigned int left_mask = rules->LeftMask();
    unsigned int right_mask = rules->RightMask();

    /* Make a list of the indices where s,t differ. */
    IndexList diff_indices;
    unsigned int num_left_diffs = 0, num_right_diffs = 0;
    unsigned int left_weight = 0, right_weight = 0;

    for (size_type i = left_mask; i < disease; ++i) {
	if (s[i] != t[i]) {
	    diff_indices.push_back(i);
	    left_weight += Marker::diff(s[i], t[i]);
	    ++num_left_diffs;
	}
    }
    for (size_type i = disease; i < right_mask; ++i) {
	if (s[i] != t[i]) {
	    diff_indices.push_back(i);
	    right_weight += Marker::diff(s[i], t[i]);
	    ++num_right_diffs;
	}
    }
    /*
     * If both sides have more differences than max_latents, then
     * max_weight path.  If one side has more than max_latents
     * differences, then recomb on that side.  If both sides have
     * fewer than max_latents differences, recomb on heavier side.
     */
    if ((num_left_diffs > _max_latents) && (num_right_diffs > _max_latents)) {
	Path path(s);
	path.Add(Path::Edge(t, std::numeric_limits<log_probability>::max()));
	return path;
    }

    if (Verbose()) {
	std::cout << "\t" << "Source: " << s << endl
		  << "\t" << "Target: " << t << endl
		  << "\t" << "Disease: " << disease << endl;
	std::cout << "\t" << "Left distance:  " << left_weight << endl
		  << "\t" << "Right distance: " << right_weight << endl;
    }
    Direction direction = LEFT;
    if ((num_right_diffs > _max_latents) ||
	((num_left_diffs <= _max_latents) && (right_weight > left_weight))) {
	direction = RIGHT;
    }
    if (Verbose()) {
	std::cout << "\t" << "Direction: ";
	switch (direction) {
	case LEFT:
	    std::cout << "LEFT" << endl;
	    break;
	case RIGHT:
	    std::cout << "RIGHT" << endl;
	    break;
	}
    }

    /* Find endpoints of range of mutatable marker indices. */
    IndexList::iterator first = diff_indices.begin();
    IndexList::iterator last = diff_indices.end();
    switch (direction) {
    case LEFT:
	if (diff_indices.size() <= _max_latents) {
	    first = last - (diff_indices.size() - 1);
	} else {
	    first = last - _max_latents;
	}
	break;
    case RIGHT:
	if (diff_indices.size() <= _max_latents) {
	    last = first + (diff_indices.size() - 1);
	} else {
	    last = first + _max_latents;
	}
	break;
    }
    return GenPath(s, t, disease, first, last, rules, Verbose());
}
