/*
 *                             The MIT License
 *
 * Wavefront Alignment Algorithms
 * Copyright (c) 2017 by Santiago Marco-Sola  <santiagomsola@gmail.com>
 *
 * This file is part of Wavefront Alignment Algorithms.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * PROJECT: Wavefront Alignment Algorithms
 * AUTHOR(S): Santiago Marco-Sola <santiagomsola@gmail.com>
 * DESCRIPTION: WaveFront alignment module for sequence pairwise alignment
 */

#include "utils/commons.h"
#include "wavefront_align.h"
#include "wavefront_unialign.h"
#include "wavefront_bialign.h"
#include "wavefront_compute.h"
#include "wavefront_compute_edit.h"
#include "wavefront_compute_linear.h"
#include "wavefront_compute_affine.h"
#include "wavefront_compute_affine2p.h"
#include "wavefront_extend.h"
#include "wavefront_backtrace.h"
#include "wavefront_debug.h"

/*
 * Checks
 */
void wavefront_align_checks(
    wavefront_aligner_t* const wf_aligner,
    const int pattern_length,
    const int text_length) {
  alignment_form_t* const form = &wf_aligner->alignment_form;
  if (wf_aligner->bialigner != NULL) {
    const bool ends_free =
        form->pattern_begin_free > 0 ||
        form->pattern_end_free > 0 ||
        form->text_begin_free > 0 ||
        form->text_end_free > 0;
    if (ends_free) {
      fprintf(stderr,"[WFA] BiWFA and ends-free is not supported yet\n");
      exit(1);
    }
  }
  const distance_metric_t distance_metric = wf_aligner->penalties.distance_metric;
  const bool is_heuristic_drop =
      (wf_aligner->heuristic.strategy & wf_heuristic_xdrop) ||
      (wf_aligner->heuristic.strategy & wf_heuristic_zdrop);
  if (is_heuristic_drop && (distance_metric==edit || distance_metric==indel)) {
    fprintf(stderr,"[WFA] Heuristics drops are not compatible with 'edit'/'indel' distance metrics\n");
    exit(1);
  }
  if (form->span == alignment_endsfree) {
    if (form->pattern_begin_free > pattern_length ||
        form->pattern_end_free > pattern_length ||
        form->text_begin_free > text_length ||
        form->text_end_free > text_length) {
      fprintf(stderr,"[WFA] Ends-free parameters must be not larger than the sequences "
          "(P0=%d,Pf=%d,T0=%d,Tf=%d). Must be (P0<=|P|,Pf<=|P|,T0<=|T|,Tf<=|T|) where (|P|,|T|)=(%d,%d)\n",
          form->pattern_begin_free,form->pattern_end_free,
          form->text_begin_free,form->text_end_free,
          pattern_length,text_length);
      exit(1);
    }
  }
}
/*
 * Wavefront Alignment Unidirectional
 */
void wavefront_align_unidirectional_cleanup(
    wavefront_aligner_t* const wf_aligner) {
  // Compute memory used
  uint64_t memory_used = wavefront_aligner_get_size(wf_aligner);
  wf_aligner->align_status.memory_used = memory_used;
  // Reap memory (controlled reaping)
  if (memory_used > wf_aligner->system.max_memory_resident) {
    // Wavefront components
    wavefront_components_reap(&wf_aligner->wf_components);
    // Check memory
    memory_used = wavefront_aligner_get_size(wf_aligner);
    wf_aligner->align_status.memory_used = memory_used;
    // Slab
    if (memory_used > wf_aligner->system.max_memory_resident) {
      wavefront_slab_reap(wf_aligner->wavefront_slab);
      if (wf_aligner->bialigner != NULL) {
        wavefront_bialigner_reap(wf_aligner->bialigner);
      }
    }
  }
}
void wavefront_align_unidirectional(
    wavefront_aligner_t* const wf_aligner,
    const char* const pattern,
    const int pattern_length,
    const char* const text,
    const int text_length) {
  // Prepare alignment
  wavefront_unialign_init(
      wf_aligner,pattern,pattern_length,text,text_length,
      affine2p_matrix_M,affine2p_matrix_M);
  // DEBUG
  wavefront_debug_prologue(wf_aligner,pattern,pattern_length,text,text_length);
  // Wavefront align sequences
  wavefront_unialign(wf_aligner);
  // Finish
  if (wf_aligner->align_status.status == WF_STATUS_MAX_SCORE_REACHED) return; // Alignment paused
  wavefront_align_unidirectional_cleanup(wf_aligner);
  // DEBUG
  wavefront_debug_epilogue(wf_aligner);
  wavefront_debug_check_correct(wf_aligner);
}
/*
 * Wavefront Alignment Bidirectional
 */
void wavefront_align_bidirectional(
    wavefront_aligner_t* const wf_aligner,
    const char* const pattern,
    const int pattern_length,
    const char* const text,
    const int text_length) {
  // DEBUG
  wavefront_debug_prologue(wf_aligner,pattern,pattern_length,text,text_length);
  // Bidirectional alignment
  wavefront_bialign(wf_aligner,pattern,pattern_length,text,text_length);
  // Finish
  const uint64_t memory_used = wavefront_aligner_get_size(wf_aligner);
  wf_aligner->align_status.memory_used = memory_used;
  // DEBUG
  wavefront_debug_epilogue(wf_aligner);
  wavefront_debug_check_correct(wf_aligner);
}
/*
 * Wavefront Alignment Dispatcher
 */
int wavefront_align(
    wavefront_aligner_t* const wf_aligner,
    const char* const pattern,
    const int pattern_length,
    const char* const text,
    const int text_length) {
  // Checks
  wavefront_align_checks(wf_aligner,pattern_length,text_length);
  // Plot
  if (wf_aligner->plot != NULL) {
    wavefront_plot_resize(wf_aligner->plot,pattern_length,text_length);
  }
  // Dispatcher
  if (wf_aligner->bialigner != NULL) {
    wavefront_align_bidirectional(wf_aligner,pattern,pattern_length,text,text_length);
  } else {
    wavefront_align_unidirectional(wf_aligner,pattern,pattern_length,text,text_length);
  }
  // Return
  return wf_aligner->align_status.status;
}
int wavefront_align_resume(
    wavefront_aligner_t* const wf_aligner) {
  // Parameters
  wavefront_align_status_t* const align_status = &wf_aligner->align_status;
  // Check current alignment status
  if (align_status->status != WF_STATUS_MAX_SCORE_REACHED ||
      wf_aligner->bialigner != NULL) {
    fprintf(stderr,"[WFA] Alignment cannot be resumed\n");
    exit(1);
  }
  // Resume aligning sequences
  wavefront_unialign(wf_aligner);
  // Finish alignment
  if (align_status->status == WF_STATUS_MAX_SCORE_REACHED) {
    return WF_STATUS_MAX_SCORE_REACHED; // Alignment paused
  }
  wavefront_align_unidirectional_cleanup(wf_aligner);
  // DEBUG
  wavefront_debug_epilogue(wf_aligner);
  wavefront_debug_check_correct(wf_aligner);
  // Return
  return align_status->status;
}
