/*
  Copyright (c) 2015 Annika Seidel <annika.seidel@studium.uni-hamburg.de>
  Copyright (c) 2015 Center for Bioinformatics, University of Hamburg

  Permission to use, copy, modify, and distribute this software for any
  purpose with or without fee is hereby granted, provided that the above
  copyright notice and this permission notice appear in all copies.

  THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

#include <ctype.h>
#include <string.h>
#include "core/assert_api.h"
#include "core/divmodmul.h"
#include "core/error.h"
#include "core/ma_api.h"
#include "core/minmax.h"
#ifdef GT_THREADS_ENABLED
#include "core/thread_api.h"
#endif
#include "core/types_api.h"
#include "extended/affinealign.h"
#include "extended/maxcoordvalue.h"
#include "extended/reconstructalignment.h"

#include "extended/linearalign_affinegapcost.h"
#define LINEAR_EDIST_GAP          ((GtUchar) UCHAR_MAX)

typedef struct {
  GtUwordPair Rstart, Dstart, Istart;
} Starttabentry;

/*-------------------------------global affine--------------------------------*/
AffineAlignEdge set_edge(GtWord Rdist, GtWord Ddist, GtWord Idist)
{
  GtUword minvalue = MIN3(Rdist, Ddist, Idist);

  if (Rdist == minvalue)
    return Affine_R;
  else if (Ddist == minvalue)
    return Affine_D;
  else if (Idist == minvalue)
    return Affine_I;

  return Affine_X;
}

static inline Rnode get_Rtabentry(const Rtabentry *rtab,
                                  AffineAlignEdge edge)
{
  if (edge == Affine_R)
  {
    return rtab->val_R;
  }
  if (edge == Affine_D)
  {
    return rtab->val_D;
  }
  gt_assert(edge == Affine_I);
  return rtab->val_I;
}

static inline void firstAtabRtabentry(AffinealignDPentry *Atabcolumn,
                                      GtUword gap_opening,
                                      AffineAlignEdge edge)
{
  Atabcolumn[0].Redge = Affine_X;
  Atabcolumn[0].Dedge = Affine_X;
  Atabcolumn[0].Iedge = Affine_X;

  switch (edge) {
  case Affine_R:
    Atabcolumn[0].Rvalue = 0;
    Atabcolumn[0].Dvalue = GT_WORD_MAX;
    Atabcolumn[0].Ivalue = GT_WORD_MAX;
    break;
  case Affine_D:
    Atabcolumn[0].Rvalue = GT_WORD_MAX;
    Atabcolumn[0].Dvalue = 0;
    Atabcolumn[0].Ivalue = GT_WORD_MAX;
    break;
  case Affine_I:
    Atabcolumn[0].Rvalue = GT_WORD_MAX;
    Atabcolumn[0].Dvalue = GT_WORD_MAX;
    Atabcolumn[0].Ivalue = 0;
    break;
  default:
    Atabcolumn[0].Rvalue = 0;
    Atabcolumn[0].Dvalue = gap_opening;
    Atabcolumn[0].Ivalue = gap_opening;
  }
}

static void firstAtabRtabcolumn(AffinealignDPentry *Atabcolumn,
                                Rtabentry *Rtabcolumn,
                                GtUword ulen,
                                GtUword gap_opening,
                                GtUword gap_extension,
                                AffineAlignEdge edge)
{
  GtUword rowindex;
  GtWord rdist, ddist,idist;
  firstAtabRtabentry(Atabcolumn, gap_opening, edge);

  Rtabcolumn[0].val_R.idx = 0;
  Rtabcolumn[0].val_D.idx = 0;
  Rtabcolumn[0].val_I.idx = 0;

  Rtabcolumn[0].val_R.edge = Affine_R;
  Rtabcolumn[0].val_D.edge = Affine_D;
  Rtabcolumn[0].val_I.edge = Affine_I;

  for (rowindex = 1; rowindex <= ulen; rowindex++)
  {
    Atabcolumn[rowindex].Rvalue = GT_WORD_MAX;
    rdist = add_safe_max(Atabcolumn[rowindex-1].Rvalue,
                         gap_opening + gap_extension);
    ddist = add_safe_max(Atabcolumn[rowindex-1].Dvalue, gap_extension);
    idist = add_safe_max(Atabcolumn[rowindex-1].Dvalue,
                         gap_opening + gap_extension);
    Atabcolumn[rowindex].Dvalue = MIN3(rdist, ddist, idist);
    Atabcolumn[rowindex].Ivalue = GT_WORD_MAX;

    Atabcolumn[rowindex].Redge = Affine_X;
    Atabcolumn[rowindex].Dedge = set_edge(rdist, ddist, idist);
    Atabcolumn[rowindex].Iedge = Affine_X;

    Rtabcolumn[rowindex].val_R.idx = rowindex;
    Rtabcolumn[rowindex].val_D.idx = rowindex;
    Rtabcolumn[rowindex].val_I.idx = rowindex;

    Rtabcolumn[rowindex].val_R.edge = Affine_R;
    Rtabcolumn[rowindex].val_D.edge = Affine_D;
    Rtabcolumn[rowindex].val_I.edge = Affine_I;
  }
}

static void nextAtabRtabcolumn(AffinealignDPentry *Atabcolumn,
                               Rtabentry *Rtabcolumn,
                               const GtScoreHandler *scorehandler,
                               const GtUchar *useq,
                               GtUword ustart,
                               GtUword ulen,
                               GtUchar b,
                               GtUword midcolumn,
                               GtUword colindex)
{
  AffinealignDPentry northwestAffinealignDPentry, westAffinealignDPentry;
  Rtabentry northwestRtabentry, westRtabentry;
  GtWord rowindex, rcost, rdist, ddist, idist, minvalue;
  GtUword gap_opening, gap_extension;

  gap_opening = gt_scorehandler_get_gap_opening(scorehandler);
  gap_extension = gt_scorehandler_get_gapscore(scorehandler);

  northwestAffinealignDPentry = Atabcolumn[0];
  northwestRtabentry = Rtabcolumn[0];

  rdist = add_safe_max(Atabcolumn[0].Rvalue, gap_extension + gap_opening);
  ddist = add_safe_max(Atabcolumn[0].Dvalue, gap_extension + gap_opening);
  idist = add_safe_max(Atabcolumn[0].Ivalue, gap_extension);

  minvalue = MIN3(rdist, ddist, idist);
  Atabcolumn[0].Ivalue = minvalue;
  Atabcolumn[0].Rvalue = GT_WORD_MAX;
  Atabcolumn[0].Dvalue = GT_WORD_MAX;

  Atabcolumn[0].Redge = Affine_X;
  Atabcolumn[0].Dedge = Affine_X;
  Atabcolumn[0].Iedge = set_edge(rdist, ddist, idist);

  if (colindex > midcolumn)
  {
    northwestRtabentry = Rtabcolumn[0];
    Rtabcolumn[0].val_R.idx = Rtabcolumn[0].val_I.idx;
    Rtabcolumn[0].val_D.idx = Rtabcolumn[0].val_I.idx;
    Rtabcolumn[0].val_I.idx = Rtabcolumn[0].val_I.idx;

    Rtabcolumn[0].val_R.edge = Affine_X;
    Rtabcolumn[0].val_D.edge = Affine_X;
    Rtabcolumn[0].val_I.edge = Rtabcolumn[0].val_I.edge;
  }

  for (rowindex = 1; rowindex <= ulen; rowindex++)
  {
    westAffinealignDPentry = Atabcolumn[rowindex];
    westRtabentry = Rtabcolumn[rowindex];

    rcost = gt_scorehandler_get_replacement(scorehandler,
                                            useq[ustart+rowindex-1], b);
    rdist = add_safe_max(northwestAffinealignDPentry.Rvalue, rcost);
    ddist = add_safe_max(northwestAffinealignDPentry.Dvalue, rcost);
    idist = add_safe_max(northwestAffinealignDPentry.Ivalue, rcost);

    minvalue = MIN3(rdist, ddist, idist);
    Atabcolumn[rowindex].Rvalue = minvalue;
    Atabcolumn[rowindex].Redge = set_edge(rdist, ddist, idist);

    rdist = add_safe_max(Atabcolumn[rowindex-1].Rvalue,
                         gap_extension + gap_opening);
    ddist = add_safe_max(Atabcolumn[rowindex-1].Dvalue,gap_extension);
    idist = add_safe_max(Atabcolumn[rowindex-1].Ivalue,
                         gap_extension + gap_opening);

    minvalue = MIN3(rdist, ddist, idist);
    Atabcolumn[rowindex].Dvalue = minvalue;
    Atabcolumn[rowindex].Dedge = set_edge(rdist, ddist, idist);

    rdist = add_safe_max(westAffinealignDPentry.Rvalue,
                         gap_extension + gap_opening);
    ddist = add_safe_max(westAffinealignDPentry.Dvalue,
                         gap_extension + gap_opening);
    idist = add_safe_max(westAffinealignDPentry.Ivalue, gap_extension);

    minvalue = MIN3(rdist, ddist, idist);
    Atabcolumn[rowindex].Ivalue = minvalue;
    Atabcolumn[rowindex].Iedge = set_edge(rdist, ddist, idist);

    if (colindex > midcolumn)
    {
      Rtabcolumn[rowindex].val_R = get_Rtabentry(&northwestRtabentry,
                                                  Atabcolumn[rowindex].Redge);
      Rtabcolumn[rowindex].val_D = get_Rtabentry(&Rtabcolumn[rowindex-1],
                                                  Atabcolumn[rowindex].Dedge);
      Rtabcolumn[rowindex].val_I = get_Rtabentry(&westRtabentry,
                                                  Atabcolumn[rowindex].Iedge);
    }
    northwestAffinealignDPentry = westAffinealignDPentry;
    northwestRtabentry = westRtabentry;
  }
}

static GtUword evaluateallAtabRtabcolumns(AffinealignDPentry *Atabcolumn,
                                          Rtabentry *Rtabcolumn,
                                          const GtScoreHandler *scorehandler,
                                          const GtUchar *useq,
                                          GtUword ustart,
                                          GtUword ulen,
                                          const GtUchar *vseq,
                                          GtUword vstart,
                                          GtUword vlen,
                                          GtUword midcolumn,
                                          AffineAlignEdge edge)
{
  GtUword colindex, gap_opening, gap_extension;

  gap_opening = gt_scorehandler_get_gap_opening(scorehandler);
  gap_extension = gt_scorehandler_get_gapscore(scorehandler);

  firstAtabRtabcolumn(Atabcolumn, Rtabcolumn, ulen,
                      gap_opening, gap_extension, edge);

  for (colindex = 1UL; colindex <= vlen; colindex++)
  {
    nextAtabRtabcolumn(Atabcolumn,
                       Rtabcolumn,
                       scorehandler,
                       useq, ustart,ulen,
                       vseq[vstart+colindex-1],
                       midcolumn,
                       colindex);
  }

  return MIN3(Atabcolumn[ulen].Rvalue,
              Atabcolumn[ulen].Dvalue,
              Atabcolumn[ulen].Ivalue);
}

AffineAlignEdge minAdditionalCosts(const AffinealignDPentry *entry,
                                   const AffineAlignEdge edge,
                                   GtUword gap_opening)
{
  GtUword rdist, ddist, idist;

  switch (edge) {
    case Affine_D:
      rdist = add_safe_max(entry->Rvalue, gap_opening);
      ddist = entry->Dvalue;
      idist = add_safe_max(entry->Ivalue, gap_opening);
     break;
    case Affine_I:
      rdist = add_safe_max(entry->Rvalue, gap_opening);
      ddist = add_safe_max(entry->Dvalue, gap_opening);
      idist = entry->Ivalue;
      break;
    default:
      rdist = entry->Rvalue;
      ddist = entry->Dvalue;
      idist = entry->Ivalue;
  }

  return set_edge(rdist, ddist, idist);
}

#ifdef GT_THREADS_ENABLED
typedef struct{
  GtLinspaceManagement *spacemanager;
  const GtScoreHandler *scorehandler;
  const GtUchar *useq, * vseq;
  GtUword ustart, ulen, vstart, vlen,
          *Ctab, rowoffset, *threadcount;
  AffineAlignEdge from_edge, to_edge;
}GtAffineCrosspointthreadinfo;

static GtAffineCrosspointthreadinfo
              set_AffineCrosspointthreadinfo(GtLinspaceManagement *spacemanager,
                                             const GtScoreHandler *scorehandler,
                                             const GtUchar *useq,
                                             GtUword ustart,
                                             GtUword ulen,
                                             const GtUchar *vseq,
                                             GtUword vstart,
                                             GtUword vlen,
                                             GtUword *Ctab,
                                             GtUword rowoffset,
                                             AffineAlignEdge from_edge,
                                             AffineAlignEdge to_edge,
                                             GtUword *threadcount)
{
  GtAffineCrosspointthreadinfo threadinfo;
  threadinfo.spacemanager = spacemanager;
  threadinfo.scorehandler = scorehandler;
  threadinfo.useq = useq;
  threadinfo.ustart = ustart;
  threadinfo.ulen = ulen;
  threadinfo.vseq = vseq;
  threadinfo.vstart = vstart;
  threadinfo.vlen = vlen;
  threadinfo.Ctab = Ctab;
  threadinfo.rowoffset = rowoffset;
  threadinfo.from_edge = from_edge;
  threadinfo.to_edge = to_edge;
  threadinfo.threadcount = threadcount;

  return threadinfo;
}
static GtUword evaluateaffinecrosspoints(GtLinspaceManagement *spacemanager,
                                         const GtScoreHandler *scorehandler,
                                         const GtUchar *useq,
                                         GtUword ustart,
                                         GtUword ulen,
                                         const GtUchar *vseq,
                                         GtUword vstart,
                                         GtUword vlen,
                                         GtUword *Ctab,
                                         GtUword rowoffset,
                                         AffineAlignEdge from_edge,
                                         AffineAlignEdge to_edge,
                                         GtUword *threadcount);

static void *evaluateaffinecrosspoints_thread_caller(void *data)
{
  GtAffineCrosspointthreadinfo *threadinfo =
                                         (GtAffineCrosspointthreadinfo *) data;
  (void) evaluateaffinecrosspoints(threadinfo->spacemanager,
                                   threadinfo->scorehandler,
                                   threadinfo->useq,
                                   threadinfo->ustart,
                                   threadinfo-> ulen,
                                   threadinfo->vseq,
                                   threadinfo->vstart,
                                   threadinfo->vlen,
                                   threadinfo->Ctab,
                                   threadinfo->rowoffset,
                                   threadinfo->from_edge,
                                   threadinfo->to_edge,
                                   threadinfo->threadcount);
  return NULL;
}
#endif

/* evaluate crosspoints in recursive way */
static GtUword evaluateaffinecrosspoints(GtLinspaceManagement *spacemanager,
                                         const GtScoreHandler *scorehandler,
                                         const GtUchar *useq,
                                         GtUword ustart,
                                         GtUword ulen,
                                         const GtUchar *vseq,
                                         GtUword vstart,
                                         GtUword vlen,
                                         GtUword *Ctab,
                                         GtUword rowoffset,
                                         AffineAlignEdge from_edge,
                                         AffineAlignEdge to_edge,
                                         GT_UNUSED GtUword *threadcount)
{
  GtUword  midrow = 0, midcol = GT_DIV2(vlen), distance, colindex;
  AffineAlignEdge bottomtype, midtype = Affine_X;
  AffinealignDPentry *Atabcolumn = NULL;
  Rtabentry *Rtabcolumn = NULL;

#ifdef GT_THREADS_ENABLED
  GtThread *t1 = NULL, *t2 = NULL;
  GtAffineCrosspointthreadinfo threadinfo1, threadinfo2;
#endif

  if (vlen >= 2UL)
  {
#ifdef GT_THREADS_ENABLED
    if (gt_jobs == 1)
    {
#endif
      if (gt_linspaceManagement_checksquare(spacemanager, ulen, vlen,
                                            sizeof (*Atabcolumn),
                                            sizeof (*Rtabcolumn)))
      {
        affine_ctab_in_square_space(spacemanager, scorehandler, Ctab,
                                    useq, ustart, ulen, vseq, vstart, vlen,
                                    rowoffset, from_edge, to_edge);
        return 0;
      }
#ifdef GT_THREADS_ENABLED
   }
#endif
    Rtabcolumn = gt_linspaceManagement_get_rTabspace(spacemanager);
    Atabcolumn = gt_linspaceManagement_get_valueTabspace(spacemanager);
    Rtabcolumn = Rtabcolumn + rowoffset;
    Atabcolumn = Atabcolumn + rowoffset;

    distance = evaluateallAtabRtabcolumns(Atabcolumn,Rtabcolumn,
                                          scorehandler,
                                          useq, ustart, ulen,
                                          vseq, vstart, vlen,
                                          midcol, from_edge);

    bottomtype = minAdditionalCosts(&Atabcolumn[ulen], to_edge,
                                 gt_scorehandler_get_gap_opening(scorehandler));
    switch (bottomtype) {
      case Affine_R:
        midrow = Rtabcolumn[ulen].val_R.idx;
        midtype = Rtabcolumn[ulen].val_R.edge;
        break;
      case Affine_D:
        midrow = Rtabcolumn[ulen].val_D.idx;
        midtype = Rtabcolumn[ulen].val_D.edge;
        break;
      case Affine_I:
        midrow = Rtabcolumn[ulen].val_I.idx;
        midtype = Rtabcolumn[ulen].val_I.edge;
        break;
      case Affine_X: /*never reach this line*/
        gt_assert(false);
    }
    Ctab[midcol] = rowoffset + midrow;
    gt_assert(midcol > 0);
    if (midrow == 0) {
      for (colindex = midcol-1; colindex > 0; colindex--)
        Ctab[colindex] = Ctab[midcol];
    }
    else{/* upper left corner */
      switch (midtype) {
        case Affine_R:
          if (midcol > 1)
            Ctab[midcol-1] = Ctab[midcol] == 0 ? 0: Ctab[midcol] - 1;

#ifdef GT_THREADS_ENABLED
          if (*threadcount + 1 > gt_jobs)
          {
#endif
            (void) evaluateaffinecrosspoints(spacemanager, scorehandler,
                                             useq, ustart, midrow-1,
                                             vseq, vstart, midcol-1,
                                             Ctab, rowoffset,
                                             from_edge, midtype,
                                             threadcount);
#ifdef GT_THREADS_ENABLED
          }
          else
          {
            threadinfo1=set_AffineCrosspointthreadinfo(spacemanager,
                                                       scorehandler,
                                                       useq, ustart, midrow-1,
                                                       vseq, vstart, midcol-1,
                                                       Ctab, rowoffset,
                                                       from_edge, midtype,
                                                       threadcount);
            (*threadcount)++;
            t1 = gt_thread_new(evaluateaffinecrosspoints_thread_caller,
                               &threadinfo1, NULL);
          }
#endif
          break;
        case Affine_D:
#ifdef GT_THREADS_ENABLED
          if (*threadcount + 1 > gt_jobs)
          {
#endif
          (void) evaluateaffinecrosspoints(spacemanager, scorehandler,
                                           useq, ustart, midrow-1,
                                           vseq, vstart, midcol,
                                           Ctab, rowoffset,
                                           from_edge,midtype,
                                           threadcount);
#ifdef GT_THREADS_ENABLED
         }
         else
         {
           threadinfo1=set_AffineCrosspointthreadinfo(spacemanager,
                                                      scorehandler,
                                                      useq, ustart, midrow-1,
                                                      vseq, vstart, midcol,
                                                      Ctab, rowoffset,
                                                      from_edge, midtype,
                                                      threadcount);

           (*threadcount)++;
           t1 = gt_thread_new(evaluateaffinecrosspoints_thread_caller,
                               &threadinfo1, NULL);
          }
#endif
          break;
        case Affine_I:
          if (midcol > 1)
            Ctab[midcol-1] = Ctab[midcol];
          (void) evaluateaffinecrosspoints(spacemanager, scorehandler,
                                           useq, ustart, midrow,
                                           vseq, vstart, midcol-1,
                                           Ctab, rowoffset,
                                           from_edge, midtype,
                                           threadcount);
          break;
        case Affine_X: /*never reach this line*/
                gt_assert(false);
      }
    }
   /*bottom right corner */
#ifdef GT_THREADS_ENABLED
    if (*threadcount + 1 > gt_jobs)
    {
#endif
      (void) evaluateaffinecrosspoints(spacemanager, scorehandler,
                                          useq, ustart+midrow, ulen-midrow,
                                          vseq, vstart+midcol, vlen-midcol,
                                          Ctab+midcol,rowoffset+midrow,
                                          midtype, to_edge, threadcount);
#ifdef GT_THREADS_ENABLED
    }
    else
    {
      threadinfo2 = set_AffineCrosspointthreadinfo(spacemanager, scorehandler,
                                                   useq, ustart + midrow,
                                                   ulen - midrow,
                                                   vseq, vstart + midcol,
                                                   vlen - midcol,
                                                   Ctab + midcol,
                                                   rowoffset + midrow,
                                                   midtype, to_edge,
                                                   threadcount);
      (*threadcount)++;
      t2 = gt_thread_new(evaluateaffinecrosspoints_thread_caller,
                         &threadinfo2, NULL);
    }

    if (t1 != NULL)
    {
      gt_thread_join(t1);
      (*threadcount)--;
      gt_thread_delete(t1);
    }
    if (t2 != NULL)
    {
      gt_thread_join(t2);
      (*threadcount)--;
      gt_thread_delete(t2);
    }

#endif
    return distance;
  }
  return 0;
}

static void affine_determineCtab0(GtUword *Ctab,
                                  GtLinspaceManagement *spacemanager,
                                  const GtScoreHandler *scorehandler,
                                  const GtUchar *useq,
                                  GtUword ustart,
                                  const GtUchar *vseq,
                                  GtUword vstart)
{
  AffinealignDPentry *Atabcolumn;

    if (Ctab[1]== 1 || Ctab[1] == 0)
      Ctab[0] = 0;
    else
    {
      gt_linspaceManagement_check(spacemanager,2*(Ctab[1]+1),Ctab[1],
                                  sizeof (*Atabcolumn),sizeof (Atabcolumn),0);
      /*gt_assert(vlen > 1);*/
      AffineAlignEdge to_edge_test = Affine_X;
      if (Ctab[1] == Ctab[2])
        to_edge_test = Affine_I;
      else
        to_edge_test = Affine_R;
      affine_ctab_in_square_space(spacemanager, scorehandler, Ctab,
                                  useq, ustart, Ctab[1], vseq, vstart,
                                  1, 0, Affine_X, to_edge_test);
    }
}

/* calculating affine alignment in linear space */
GtUword gt_calc_affinealign_linear(GtLinspaceManagement *spacemanager,
                                   const GtScoreHandler *scorehandler,
                                   GtAlignment *align,
                                   const GtUchar *useq,
                                   GtUword ustart,
                                   GtUword ulen,
                                   const GtUchar *vseq,
                                   GtUword vstart,
                                   GtUword vlen)
{
  GtUword distance, *Ctab, threadcount = 1;
  GtWord gap_extension, gap_opening;
  AffinealignDPentry *Atabcolumn;
  Rtabentry *Rtabcolumn;

  gt_linspaceManagement_set_ulen(spacemanager, ulen);
  gap_extension = gt_scorehandler_get_gapscore(scorehandler);
  gap_opening = gt_scorehandler_get_gap_opening(scorehandler);
  if (ulen == 0UL)
  {
      distance = construct_trivial_insertion_alignment(align, vlen,
                                                      gap_extension);
      distance += gap_opening;
      return distance;
  }
  else if (vlen == 0UL)
  {
      distance = construct_trivial_deletion_alignment(align, ulen,
                                                      gap_extension);
      distance += gap_opening;
      return distance;
  }
  else if (vlen == 1UL)
  {
     gt_linspaceManagement_check(spacemanager, (ulen+1)*(vlen+1)-1, ulen,
                                 sizeof (*Atabcolumn), sizeof (Atabcolumn), 0);
    return gt_affinealign_with_Management(spacemanager, scorehandler, align,
                                   useq+ustart, ulen, vseq+vstart, vlen);
  }
  if (gt_linspaceManagement_checksquare(spacemanager, ulen, vlen,
                                     sizeof (*Atabcolumn),sizeof (*Rtabcolumn)))
  {
    return gt_affinealign_with_Management(spacemanager, scorehandler, align,
                                   useq+ustart, ulen, vseq+vstart, vlen);
  }
  else
  {
    gt_linspaceManagement_check(spacemanager, ulen, vlen, sizeof (*Atabcolumn),
                                sizeof (*Rtabcolumn), sizeof (*Ctab));
    Ctab = gt_linspaceManagement_get_crosspointTabspace(spacemanager);
    Ctab[vlen] = ulen;
    distance = evaluateaffinecrosspoints(spacemanager, scorehandler,
                                         useq, ustart, ulen,
                                         vseq, vstart, vlen,
                                         Ctab, 0, Affine_X,
                                         Affine_X, &threadcount);

    affine_determineCtab0(Ctab, spacemanager, scorehandler,
                          useq, ustart, vseq, vstart);

    reconstructalignment_from_Ctab(align,Ctab,useq,ustart,vseq,
                                   vstart,vlen,scorehandler);

  }
  return distance;
}

/* global alignment with affine gapcosts in linear space */
GtUword gt_computeaffinelinearspace_generic(GtLinspaceManagement *spacemanager,
                                            const GtScoreHandler *scorehandler,
                                            GtAlignment *align,
                                            const GtUchar *useq,
                                            GtUword ustart,
                                            GtUword ulen,
                                            const GtUchar *vseq,
                                            GtUword vstart,
                                            GtUword vlen)
{
  GtUword distance;
  gt_assert(spacemanager && scorehandler && align);

  gt_alignment_set_seqs(align,useq+ustart, ulen, vseq+vstart, vlen);
  distance = gt_calc_affinealign_linear(spacemanager, scorehandler, align,
                                        useq, ustart, ulen,
                                        vseq, vstart, vlen);
  return distance;
}

/* global alignment with affine gapcosts in linear space
   with constant cost values*/
GtUword gt_computeaffinelinearspace(GtLinspaceManagement *spacemanager,
                                    GtAlignment *align,
                                    const GtUchar *useq,
                                    GtUword ustart,
                                    GtUword ulen,
                                    const GtUchar *vseq,
                                    GtUword vstart,
                                    GtUword vlen,
                                    GtUword matchcost,
                                    GtUword mismatchcost,
                                    GtUword gap_opening,
                                    GtUword gap_extension)
{
  GtUword distance;
  GtScoreHandler *scorehandler = gt_scorehandler_new(matchcost,
                                                     mismatchcost,
                                                     gap_opening,
                                                     gap_extension);

  gt_alignment_set_seqs(align,useq+ustart, ulen, vseq+vstart, vlen);
  distance = gt_computeaffinelinearspace_generic(spacemanager,
                                                 scorehandler, align,
                                                 useq, ustart, ulen,
                                                 vseq, vstart, vlen);
  gt_scorehandler_delete(scorehandler);
  return distance;
}

/*------------------------------local affine--------------------------------*/
static void firstAStabcolumn(AffinealignDPentry *Atabcolumn,
                             Starttabentry *Starttabcolumn,
                             const GtScoreHandler *scorehandler,
                             GtUword ulen)
{
  GtUword rowindex;
  GtWord gap_opening, gap_extension;

  gap_opening = gt_scorehandler_get_gap_opening(scorehandler);
  gap_extension = gt_scorehandler_get_gapscore(scorehandler);

  Atabcolumn[0].Rvalue = GT_WORD_MIN;
  Atabcolumn[0].Dvalue = GT_WORD_MIN;
  Atabcolumn[0].Ivalue = GT_WORD_MIN;
  Atabcolumn[0].totalvalue = 0;

  Starttabcolumn[0].Rstart.a = 0;
  Starttabcolumn[0].Rstart.b = 0;
  Starttabcolumn[0].Dstart.a = 0;
  Starttabcolumn[0].Dstart.b = 0;
  Starttabcolumn[0].Istart.a = 0;
  Starttabcolumn[0].Istart.b = 0;

  for (rowindex = 1; rowindex <= ulen; rowindex++)
  {
    Atabcolumn[rowindex].Rvalue = GT_WORD_MIN;
    Atabcolumn[rowindex].Dvalue = (gap_opening + gap_extension);
    Atabcolumn[rowindex].Ivalue = GT_WORD_MIN;
    Atabcolumn[rowindex].totalvalue = 0;

    Starttabcolumn[rowindex].Rstart.a = rowindex;
    Starttabcolumn[rowindex].Rstart.b = 0;
    Starttabcolumn[rowindex].Dstart.a = rowindex;
    Starttabcolumn[rowindex].Dstart.b = 0;
    Starttabcolumn[rowindex].Istart.a = rowindex;
    Starttabcolumn[rowindex].Istart.b = 0;
  }
}

static GtUwordPair setStarttabentry(GtWord entry, AffinealignDPentry *Atab,
                                    Starttabentry *Stab,
                                    GtWord replacement,
                                    GtWord gap_opening,
                                    GtWord gap_extension,
                                    const AffineAlignEdge edge)
{
  GtUwordPair start;
  switch (edge) {
    case Affine_R:
      if (entry == Atab->Rvalue + replacement)
         start = Stab->Rstart;
      else if (entry == Atab->Dvalue + replacement)
         start = Stab->Dstart;
      else if (entry == Atab->Ivalue + replacement)
         start = Stab->Istart;
      else
        start = Stab->Rstart;
      break;
    case Affine_D:
      if (entry == Atab->Rvalue + gap_opening + gap_extension)
         start = Stab->Rstart;
      else if (entry == Atab->Dvalue + gap_extension)
         start = Stab->Dstart;
      else if (entry == Atab->Ivalue + gap_opening + gap_extension)
         start = Stab->Istart;
      else
        start = Stab->Rstart;
      break;
    case Affine_I:
      if (entry == Atab->Rvalue + gap_opening + gap_extension)
         start = Stab->Rstart;
      else if (entry == Atab->Dvalue + gap_opening + gap_extension)
         start = Stab->Dstart;
      else if (entry == Atab->Ivalue + gap_extension)
         start = Stab->Istart;
      else
        start = Stab->Rstart;
      break;
    default:
      start.a = 0;
      start.b = 0;
  }
  return start;
}

static void nextAStabcolumn(AffinealignDPentry *Atabcolumn,
                            Starttabentry *Starttabcolumn,
                            const GtScoreHandler *scorehandler,
                            const GtUchar *useq, GtUword ustart,
                            GtUword ulen,
                            GtUchar b,
                            GtUword colindex,
                            GtMaxcoordvalue *max)
{
  AffinealignDPentry northwestAffinealignDPentry, westAffinealignDPentry;
  Starttabentry Snw, Swe;
  GtUword rowindex;
  GtWord gap_extension, gap_opening, replacement, temp, val1, val2;
  GtUwordPair start = {0};

  gap_opening = gt_scorehandler_get_gap_opening(scorehandler);
  gap_extension = gt_scorehandler_get_gapscore(scorehandler);

  northwestAffinealignDPentry = Atabcolumn[0];
  Snw = Starttabcolumn[0];
  Atabcolumn[0].Rvalue = GT_WORD_MIN;
  Atabcolumn[0].Dvalue = GT_WORD_MIN;
  Atabcolumn[0].Ivalue = (gap_opening + gap_extension);
  temp = MAX3(Atabcolumn[0].Rvalue,
              Atabcolumn[0].Dvalue,
              Atabcolumn[0].Ivalue);
  Atabcolumn[0].totalvalue = ((temp > 0)? temp : 0);

  if (Atabcolumn[0].totalvalue == 0) {
    Starttabcolumn[0].Rstart.a = 0;
    Starttabcolumn[0].Rstart.b = colindex;
    Starttabcolumn[0].Dstart.a = 0;
    Starttabcolumn[0].Dstart.b = colindex;
    Starttabcolumn[0].Istart.a = 0;
    Starttabcolumn[0].Istart.b = colindex;
  }

  if (Atabcolumn[0].totalvalue > gt_max_get_value(max))
    {
      if (Atabcolumn[0].totalvalue == Atabcolumn[0].Rvalue)
         start = Starttabcolumn[0].Rstart;
      else if (Atabcolumn[0].totalvalue == Atabcolumn[0].Dvalue)
         start = Starttabcolumn[0].Dstart;
      else if (Atabcolumn[0].totalvalue == Atabcolumn[0].Ivalue)
         start = Starttabcolumn[0].Istart;

      gt_max_coord_update(max, Atabcolumn[0].totalvalue,
                          start, 0, colindex);
    }
  for (rowindex = 1; rowindex <= ulen; rowindex++)
  {
    westAffinealignDPentry = Atabcolumn[rowindex];
    Swe = Starttabcolumn[rowindex];

    /*calculate Rvalue*/
    replacement = gt_scorehandler_get_replacement(scorehandler,
                                                  useq[ustart+rowindex-1], b);

    Atabcolumn[rowindex].Rvalue =
              add_safe_min(northwestAffinealignDPentry.totalvalue, replacement);
    Starttabcolumn[rowindex].Rstart =
    setStarttabentry(Atabcolumn[rowindex].Rvalue, &northwestAffinealignDPentry,
                      &Snw, replacement,gap_opening, gap_extension, Affine_R);

    /*calculate Dvalue*/
    val1 = add_safe_min(Atabcolumn[rowindex-1].Dvalue, gap_extension);
    val2 = add_safe_min(Atabcolumn[rowindex-1].totalvalue,
                       (gap_opening+gap_extension));
    Atabcolumn[rowindex].Dvalue = MAX(val1,val2);
    Starttabcolumn[rowindex].Dstart =
    setStarttabentry(Atabcolumn[rowindex].Dvalue, &Atabcolumn[rowindex-1],
                     &Starttabcolumn[rowindex-1], replacement, gap_opening,
                     gap_extension,Affine_D);

    /*calculate Ivalue*/
    val1=(add_safe_min(westAffinealignDPentry.Ivalue,gap_extension));
    val2=(add_safe_min(westAffinealignDPentry.totalvalue,
                              gap_opening+gap_extension));
    Atabcolumn[rowindex].Ivalue = MAX(val1,val2);
    Starttabcolumn[rowindex].Istart =
    setStarttabentry(Atabcolumn[rowindex].Ivalue, &westAffinealignDPentry, &Swe,
                     replacement, gap_opening, gap_extension, Affine_I);

    /*calculate totalvalue*/
    temp = MAX3(Atabcolumn[rowindex].Rvalue,
                Atabcolumn[rowindex].Dvalue,
                Atabcolumn[rowindex].Ivalue);
    Atabcolumn[rowindex].totalvalue = temp > 0 ? temp : 0;

    /* set start indices for Atab-values*/
    if (Atabcolumn[rowindex].totalvalue == 0)
    {
      Starttabcolumn[rowindex].Rstart.a = rowindex;
      Starttabcolumn[rowindex].Rstart.b = colindex;
      Starttabcolumn[rowindex].Dstart.a = rowindex;
      Starttabcolumn[rowindex].Dstart.b = colindex;
      Starttabcolumn[rowindex].Istart.a = rowindex;
      Starttabcolumn[rowindex].Istart.b = colindex;
    }

    /*set new max*/
    if (Atabcolumn[rowindex].totalvalue > gt_max_get_value(max))
    {
      if (Atabcolumn[rowindex].totalvalue == Atabcolumn[rowindex].Rvalue)
         start = Starttabcolumn[rowindex].Rstart;
      else if (Atabcolumn[rowindex].totalvalue == Atabcolumn[rowindex].Dvalue)
         start = Starttabcolumn[rowindex].Dstart;
      else if (Atabcolumn[rowindex].totalvalue == Atabcolumn[rowindex].Ivalue)
         start = Starttabcolumn[rowindex].Istart;

      gt_max_coord_update(max, Atabcolumn[rowindex].totalvalue,
                          start, rowindex, colindex);
    }
    northwestAffinealignDPentry=westAffinealignDPentry;
    Snw=Swe;
  }
}

static GtMaxcoordvalue *evaluateallAStabcolumns(GtLinspaceManagement *space,
                                            const GtScoreHandler *scorehandler,
                                            const GtUchar *useq,
                                            GtUword ustart,
                                            GtUword ulen,
                                            const GtUchar *vseq,
                                            GtUword vstart,
                                            GtUword vlen)
{
  GtUword colindex;
  GtMaxcoordvalue *max;
  AffinealignDPentry *Atabcolumn;
  Starttabentry *Starttabcolumn;

  Atabcolumn = gt_linspaceManagement_get_valueTabspace(space);
  Starttabcolumn = gt_linspaceManagement_get_rTabspace(space);

  firstAStabcolumn(Atabcolumn, Starttabcolumn, scorehandler, ulen);

  max = gt_linspaceManagement_get_maxspace(space);
  for (colindex = 1UL; colindex <= vlen; colindex++)
  {
    nextAStabcolumn(Atabcolumn, Starttabcolumn, scorehandler, useq, ustart,
                    ulen, vseq[vstart+colindex-1], colindex, max);
  }
  return max;
}

/* determining start and end of local alignment and call global function */
GtWord gt_computeaffinelinearspace_local_generic(
                                            GtLinspaceManagement *spacemanager,
                                            const GtScoreHandler *scorehandler,
                                            GtAlignment *align,
                                            const GtUchar *useq,
                                            GtUword ustart,
                                            GtUword ulen,
                                            const GtUchar *vseq,
                                            GtUword vstart,
                                            GtUword vlen)
{
  GtUword ulen_part, ustart_part, vlen_part, vstart_part;
  GtWord score;
  AffinealignDPentry *Atabcolumn;
  Starttabentry *Starttabcolumn;
  GtMaxcoordvalue *max;

  gt_linspaceManagement_set_ulen(spacemanager, ulen);
  if (ulen == 0UL || vlen == 0UL)
  {
     /* empty alignment */
    return 0;
  }
  else if (vlen == 1UL)
  {
    gt_linspaceManagement_check_local(spacemanager,
                                    (ulen+1)*(vlen+1)-1, ulen,
                                    sizeof (*Atabcolumn),
                                    sizeof (Atabcolumn));
    return affinealign_in_square_space_local_generic(spacemanager,scorehandler,
                                                   align,
                                                   useq, ustart, ulen,
                                                   vseq, vstart, vlen
                                                   );
  }
  else if (gt_linspaceManagement_checksquare_local(spacemanager, ulen, vlen,
                                                   sizeof (*Atabcolumn),
                                                   sizeof (*Starttabcolumn)))
  {
    /* call alignment function for square space */
    return affinealign_in_square_space_local_generic(spacemanager, scorehandler,
                                                     align, useq, ustart, ulen,
                                                     vseq, vstart, vlen);
  }

  gt_linspaceManagement_check_local(spacemanager, ulen, vlen,
                                    sizeof (*Atabcolumn),
                                    sizeof (*Starttabcolumn));

  max = evaluateallAStabcolumns(spacemanager, scorehandler,
                                useq, ustart, ulen,
                                vseq, vstart, vlen);

  score = gt_max_get_value(max);

  if (gt_max_get_length_safe(max))
  {
    GtScoreHandler *costhandler = gt_scorehandler2costhandler(scorehandler);

    ustart_part = ustart+(gt_max_get_start(max)).a;
    vstart_part = vstart+(gt_max_get_start(max)).b;
    ulen_part = gt_max_get_row_length(max);
    vlen_part = gt_max_get_col_length(max);

    gt_alignment_set_seqs(align,useq + ustart_part,ulen_part,
                                vseq + vstart_part,vlen_part);

    /* change score to cost, from maximizing to minimizing */

    gt_calc_affinealign_linear(spacemanager,
                               costhandler,
                               align,
                               useq, ustart_part, ulen_part,
                               vseq, vstart_part, vlen_part);
    gt_scorehandler_delete(costhandler);
  } else
  {
     /* empty alignment */
     return 0;
  }
  return score;
}

/* local alignment with linear gapcosts in linear space with constant costs*/
GtWord gt_computeaffinelinearspace_local(GtLinspaceManagement *spacemanager,
                                         GtAlignment *align,
                                         const GtUchar *useq,
                                         GtUword ustart,
                                         GtUword ulen,
                                         const GtUchar *vseq,
                                         GtUword vstart,
                                         GtUword vlen,
                                         GtWord matchscore,
                                         GtWord mismatchscore,
                                         GtWord gap_opening,
                                         GtWord gap_extension)
{
  GtWord score;

  GtScoreHandler *scorehandler = gt_scorehandler_new(matchscore,
                                                     mismatchscore,
                                                     gap_opening,
                                                     gap_extension);

  score = gt_computeaffinelinearspace_local_generic(spacemanager, scorehandler,
                                                    align,
                                                    useq, ustart, ulen,
                                                    vseq, vstart, vlen);
  gt_scorehandler_delete(scorehandler);
  return score;
}

/*----------------------------checkfunctions--------------------------*/
void gt_checkaffinelinearspace(GT_UNUSED bool forward,
                               const GtUchar *useq,
                               GtUword ulen,
                               const GtUchar *vseq,
                               GtUword vlen)
{
  GtAlignment *align;
  GtUword affine_score1, affine_score2, affine_score3,
          matchcost = 0, mismatchcost = 4, gap_opening = 4, gap_extension = 1;
  GtLinspaceManagement *spacemanager;
  GtScoreHandler *scorehandler;

  gt_assert(useq && vseq);
  if (memchr(useq, LINEAR_EDIST_GAP,ulen) != NULL)
  {
    fprintf(stderr,"%s: sequence u contains gap symbol\n",__func__);
    exit(GT_EXIT_PROGRAMMING_ERROR);
  }
  if (memchr(vseq, LINEAR_EDIST_GAP,vlen) != NULL)
  {
    fprintf(stderr,"%s: sequence v contains gap symbol\n",__func__);
    exit(GT_EXIT_PROGRAMMING_ERROR);
  }

  /* affinealign (square) handles lower/upper cases  in another way*/
  scorehandler = gt_scorehandler_new(matchcost, mismatchcost,
                                     gap_opening, gap_extension);
  gt_scorehandler_plain(scorehandler);
  align = gt_alignment_new_with_seqs(useq, ulen, vseq, vlen);
  spacemanager = gt_linspaceManagement_new();

  affine_score1 = gt_calc_affinealign_linear(spacemanager, scorehandler, align,
                                             useq, 0, ulen,
                                             vseq, 0, vlen);
  gt_linspaceManagement_delete(spacemanager);
  affine_score2 = gt_alignment_eval_with_affine_score(align,  true,matchcost,
                                                      mismatchcost, gap_opening,
                                                      gap_extension);
  gt_alignment_delete(align);
  gt_scorehandler_delete(scorehandler);
  if (affine_score1 != affine_score2)
  {
    fprintf(stderr,"gt_calc_affinealign_linear = "GT_WU" != "GT_WU
            " = gt_alignment_eval_with_affine_score\n", affine_score1,
                                                        affine_score2);
    exit(GT_EXIT_PROGRAMMING_ERROR);
  }

  align = gt_affinealign(useq, ulen, vseq, vlen, matchcost,
                         mismatchcost, gap_opening, gap_extension);

  affine_score3 = gt_alignment_eval_with_affine_score(align,  true, matchcost,
                                                      mismatchcost, gap_opening,
                                                      gap_extension);

  if (affine_score1 != affine_score3)
  {
    fprintf(stderr,"gt_calc_affinealign_linear = "GT_WU" != "GT_WU
            " = gt_affinealign\n", affine_score1, affine_score3);
    exit(GT_EXIT_PROGRAMMING_ERROR);
  }
  gt_alignment_delete(align);
}

void gt_checkaffinelinearspace_local(GT_UNUSED bool forward,
                                     const GtUchar *useq,
                                     GtUword ulen,
                                     const GtUchar *vseq,
                                     GtUword vlen)
{
  GtAlignment *align;
  GtWord affine_score1, affine_score2, affine_score3, affine_score4,
         matchscore = 6, mismatchscore = -3,
         gap_opening = -2, gap_extension = -1;
  GtScoreHandler *scorehandler;
  GtLinspaceManagement *spacemanager;

  gt_assert(useq && vseq);
  if (memchr(useq, LINEAR_EDIST_GAP,ulen) != NULL)
  {
    fprintf(stderr,"%s: sequence u contains gap symbol\n",__func__);
    exit(GT_EXIT_PROGRAMMING_ERROR);
  }
  if (memchr(vseq, LINEAR_EDIST_GAP,vlen) != NULL)
  {
    fprintf(stderr,"%s: sequence v contains gap symbol\n",__func__);
    exit(GT_EXIT_PROGRAMMING_ERROR);
  }

  scorehandler = gt_scorehandler_new(matchscore, mismatchscore,
                                     gap_opening, gap_extension);
  align = gt_alignment_new();
  spacemanager = gt_linspaceManagement_new();
  affine_score1 = gt_computeaffinelinearspace_local_generic(spacemanager,
                                                            scorehandler, align,
                                                            useq, 0, ulen,
                                                            vseq,  0, vlen);
  gt_linspaceManagement_delete(spacemanager);
  gt_scorehandler_delete(scorehandler);

  affine_score2 = gt_alignment_eval_with_affine_score(align, true, matchscore,
                                    mismatchscore, gap_opening, gap_extension);

  if (affine_score1 != affine_score2)
  {
    fprintf(stderr,"gt_calc_affinealign_linear_local = "GT_WD" != "GT_WD
            " = gt_alignment_eval_with_affine_score\n", affine_score1,
                                                        affine_score2);
    exit(GT_EXIT_PROGRAMMING_ERROR);
  }
  gt_alignment_reset(align);
  affine_score3 = affinealign_in_square_space_local(NULL, align, useq, 0,
                                                    ulen, vseq, 0, vlen,
                                                    matchscore, mismatchscore,
                                                    gap_opening, gap_extension);

  if (affine_score1 != affine_score3)
  {
    fprintf(stderr,"gt_calc_affinealign_linear_local = "GT_WD" != "GT_WD
            " = affinealign_in_square_space_local\n", affine_score1,
                                                        affine_score3);
    exit(GT_EXIT_PROGRAMMING_ERROR);
  }

  affine_score4 = gt_alignment_eval_with_affine_score(align, true, matchscore,
                                    mismatchscore, gap_opening, gap_extension);

  if (affine_score3 != affine_score4)
  {
    fprintf(stderr,"affinealign_in_square_space_local = "GT_WD" != "GT_WD
            " = gt_alignment_eval_generic_with_affine_score\n", affine_score3,
                                                        affine_score4);
    exit(GT_EXIT_PROGRAMMING_ERROR);
  }
  gt_alignment_delete(align);
}
