#!/usr/bin/env ruby

def getc_param(key)
  if key == "PLAINSEQ" or key == "BARE_ENCSEQ"
    return "const GtUchar *plainseq"
  elsif key == "INTSEQ"
    return "const GtUsainindextype *array"
  else
    return "const GtEncseq *encseq"
  end
end

def getc_argument(key)
  if key == "PLAINSEQ" or key == "BARE_ENCSEQ"
    return "plainseq"
  elsif key == "INTSEQ"
    return "array"
  else
    return "encseq"
  end
end

def getc_call(key,posexpr,seqaccess = false)
  if key == "PLAINSEQ"
    return "(GtUword)\nplainseq[#{posexpr}]"
  elsif key == "INTSEQ"
    return "(GtUword) array[#{posexpr}]"
  elsif key == "BARE_ENCSEQ"
    if seqaccess
      return "(GtUword)\nGT_ISSPECIAL(tmpcc =\nplainseq[#{posexpr}])\n" +
             "  ? GT_UNIQUEINT(#{posexpr}) : (GtUword) tmpcc"
    else
      return "(GtUword)\nplainseq[#{posexpr}]"
    end
  elsif seqaccess
    return "GT_ISSPECIAL(tmpcc =\ngt_encseq_reader_next_encoded_char(esr))\n" +
               " ? GT_UNIQUEINT(#{posexpr}) : (GtUword) tmpcc"
  else
    return "(GtUword) gt_encseq_get_encoded_char(\n" +
                                  "encseq,\n" +
                                  "(GtUword) (#{posexpr}),\n" +
                                  "sainseq->readmode)"
  end
end

def special2unique(key,cc,start)
  if key == "ENCSEQ" || key == "BARE_ENCSEQ"
    return "if (GT_ISSPECIAL(#{cc})) { #{cc} = GT_UNIQUEINT(#{start}); }"
  else
    return ""
  end
end

def declare_tmpvars(key,seqaccess = false)
  if seqaccess and key == "ENCSEQ"
    return "GtUchar tmpcc;\nGtEncseqReader *esr;"
  elsif seqaccess and key == "BARE_ENCSEQ"
    return "GtUchar tmpcc;"
  else
    return ""
  end
end

def sstarfirstcharcount_bucketsize_assert_not_NULL(key,which)
  if which.member?(key)
    return "gt_assert(bucketsize != NULL && sstarfirstcharcount != NULL);"
  else
    return ""
  end
end

def sstarfirstcharcount_update(key,struct,which)
  if which.member?(key)
    if struct
      return "sainseq->sstarfirstcharcount[nextcc]++;"
    else
      return "sstarfirstcharcount[nextcc]++;"
    end
  else
    return ""
  end
end

def bucketsize_update(key,which)
  if which.member?(key)
    return "bucketsize[currentcc]++;"
  else
    return ""
  end
end

def init_esr(key)
  if key == "ENCSEQ"
    return "esr = gt_encseq_create_reader_with_readmode(
                            encseq,
                            gt_readmode_inverse_direction(sainseq->readmode),
                            0);"
  else
    return ""
  end
end

def delete_esr(key)
  if key == "ENCSEQ"
    return "gt_encseq_reader_delete(esr);"
  else
    return ""
  end
end

begin
  fo = File.open("src/match/sfx-sain.inc","w")
rescue => err
  STDERR.puts "#{$0}: #{err}"
  exit 1
end

fo.puts "/* Do not edit this file, as it is generated by #{__FILE__}.\n*/"

["PLAINSEQ","ENCSEQ","INTSEQ"].each do |key|
fo.puts <<CCODE

static void gt_sain_#{key}_fast_induceLtypesuffixes1(GtSainseq *sainseq,
                                                 #{getc_param(key)},
                                         GtSsainindextype *suftab,
                                         GtUword nonspecialentries)
{
  GtUword lastupdatecc = 0;
  GtSsainindextype *suftabptr, *bucketptr = NULL;
#{declare_tmpvars(key)}
  GtUsainindextype *fillptr = sainseq->bucketfillptr;

  gt_assert(sainseq->roundtable != NULL);
  for (suftabptr = suftab, sainseq->currentround = 0;
       suftabptr < suftab + nonspecialentries; suftabptr++)
  {
    GtSsainindextype position;
    if ((position = *suftabptr) > 0)
    {
      GtUword currentcc;

      if (position >= (GtSsainindextype) sainseq->totallength)
      {
        sainseq->currentround++;
        position -= (GtSsainindextype) sainseq->totallength;
      }
      currentcc = #{getc_call(key,"position")};
      if (currentcc < sainseq->numofchars)
      {
        if (position > 0)
        {
          GtUword t, leftcontextcc;

          position--;
          leftcontextcc = #{getc_call(key,"position")};
          t = (currentcc << 1) | (leftcontextcc < currentcc ? 1UL : 0);
          gt_assert(currentcc > 0 &&
                    sainseq->roundtable[t] <= sainseq->currentround);
          if (sainseq->roundtable[t] < sainseq->currentround)
          {
            position += (GtSsainindextype) sainseq->totallength;
            sainseq->roundtable[t] = sainseq->currentround;
          }
          GT_SAINUPDATEBUCKETPTR(currentcc);
          /* negative => position does not derive L-suffix
             positive => position may derive L-suffix */
          gt_assert(suftabptr < bucketptr);
          *bucketptr++ = (t & 1UL) ? ~position : position;
          *suftabptr = 0;
#ifdef SAINSHOWSTATE
          gt_assert(bucketptr != NULL);
          printf("L-induce: suftab[" GT_WU "]=" GT_WD "\\n",
                  (GtUword) (bucketptr-1-suftab),*(bucketptr-1));
#endif
        }
      } else
      {
        *suftabptr = 0;
      }
    } else
    {
      if (position < 0)
      {
        *suftabptr = ~position;
      }
    }
  }
}

static void gt_sain_#{key}_induceLtypesuffixes1(GtSainseq *sainseq,
                                                 #{getc_param(key)},
                                         GtSsainindextype *suftab,
                                         GtUword nonspecialentries)
{
  GtUword lastupdatecc = 0;
  GtUsainindextype *fillptr = sainseq->bucketfillptr;
#{declare_tmpvars(key)}
  GtSsainindextype *suftabptr, *bucketptr = NULL;

  gt_assert(sainseq->roundtable == NULL);
  for (suftabptr = suftab; suftabptr < suftab + nonspecialentries; suftabptr++)
  {
    GtSsainindextype position;
    if ((position = *suftabptr) > 0)
    {
      GtUword currentcc = #{getc_call(key,"position")};
      if (currentcc < sainseq->numofchars)
      {
        if (position > 0)
        {
          GtUword leftcontextcc;

          position--;
          leftcontextcc = #{getc_call(key,"position")};
          GT_SAINUPDATEBUCKETPTR(currentcc);
          /* negative => position does not derive L-suffix
             positive => position may derive L-suffix */
          gt_assert(suftabptr < bucketptr);
          *bucketptr++ = (leftcontextcc < currentcc) ? ~position : position;
          *suftabptr = 0;
#ifdef SAINSHOWSTATE
          gt_assert(bucketptr != NULL);
          printf("L-induce: suftab[" GT_WU "]=" GT_WD "\\n",
                  (GtUword) (bucketptr-1-suftab),*(bucketptr-1));
#endif
        }
      } else
      {
        *suftabptr = 0;
      }
    } else
    {
      if (position < 0)
      {
        *suftabptr = ~position;
      }
    }
  }
}

static void gt_sain_#{key}_fast_induceStypesuffixes1(GtSainseq *sainseq,
                                                 #{getc_param(key)},
                                         GtSsainindextype *suftab,
                                         GtUword nonspecialentries)
{
  GtUword lastupdatecc = 0;
  GtUsainindextype *fillptr = sainseq->bucketfillptr;
#{declare_tmpvars(key)}
  GtSsainindextype *suftabptr, *bucketptr = NULL;

  gt_assert(sainseq->roundtable != NULL);
  gt_sain_special_singleSinduction1(sainseq,
                                    suftab,
                                    (GtSsainindextype)
                                    (sainseq->totallength-1));
  if (sainseq->seqtype == GT_SAIN_ENCSEQ ||
      sainseq->seqtype == GT_SAIN_BARE_ENCSEQ)
  {
    gt_sain_induceStypes1fromspecialranges(sainseq,suftab);
  }
  for (suftabptr = suftab + nonspecialentries - 1; suftabptr >= suftab;
       suftabptr--)
  {
    GtSsainindextype position;
    if ((position = *suftabptr) > 0)
    {
      if (position >= (GtSsainindextype) sainseq->totallength)
      {
        sainseq->currentround++;
        position -= (GtSsainindextype) sainseq->totallength;
      }
      if (position > 0)
      {
        GtUword currentcc = #{getc_call(key,"position")};
        if (currentcc < sainseq->numofchars)
        {
          GtUword t, leftcontextcc;

          position--;
          leftcontextcc = #{getc_call(key,"position")};
          t = (currentcc << 1) | (leftcontextcc > currentcc ? 1UL : 0);
          gt_assert(sainseq->roundtable[t] <= sainseq->currentround);
          if (sainseq->roundtable[t] < sainseq->currentround)
          {
            position += (GtSsainindextype) sainseq->totallength;
            sainseq->roundtable[t] = sainseq->currentround;
          }
          GT_SAINUPDATEBUCKETPTR(currentcc);
          gt_assert(bucketptr != NULL && bucketptr - 1 < suftabptr);
          *(--bucketptr) = (t & 1UL) ? ~(position+1) : position;
#ifdef SAINSHOWSTATE
          printf("S-induce: suftab[" GT_WU "]=" GT_WD "\\n",
                  (GtUword) (bucketptr - suftab),*bucketptr);
#endif
        }
      }
      *suftabptr = 0;
    }
  }
}

static void gt_sain_#{key}_induceStypesuffixes1(GtSainseq *sainseq,
                                                 #{getc_param(key)},
                                         GtSsainindextype *suftab,
                                         GtUword nonspecialentries)
{
  GtUword lastupdatecc = 0;
  GtUsainindextype *fillptr = sainseq->bucketfillptr;
#{declare_tmpvars(key)}
  GtSsainindextype *suftabptr, *bucketptr = NULL;

  gt_assert(sainseq->roundtable == NULL);
  gt_sain_special_singleSinduction1(sainseq,
                                    suftab,
                                    (GtSsainindextype)
                                    (sainseq->totallength-1));
  if (sainseq->seqtype == GT_SAIN_ENCSEQ ||
      sainseq->seqtype == GT_SAIN_BARE_ENCSEQ)
  {
    gt_sain_induceStypes1fromspecialranges(sainseq,suftab);
  }
  for (suftabptr = suftab + nonspecialentries - 1; suftabptr >= suftab;
       suftabptr--)
  {
    GtSsainindextype position;
    if ((position = *suftabptr) > 0)
    {
      GtUword currentcc = #{getc_call(key,"position")};
      if (currentcc < sainseq->numofchars)
      {
        GtUword leftcontextcc;

        position--;
        leftcontextcc = #{getc_call(key,"position")};
        GT_SAINUPDATEBUCKETPTR(currentcc);
        gt_assert(bucketptr != NULL && bucketptr - 1 < suftabptr);
        *(--bucketptr) = (leftcontextcc > currentcc)
                          ? ~(position+1) : position;
#ifdef SAINSHOWSTATE
        printf("S-induce: suftab[" GT_WU "]=" GT_WD "\\n",
               (GtUword) (bucketptr - suftab),*bucketptr);
#endif
      }
      *suftabptr = 0;
    }
  }
}

static void gt_sain_#{key}_induceLtypesuffixes2(const GtSainseq *sainseq,
                                                 #{getc_param(key)},
                                         GtSsainindextype *suftab,
                                         GtUword nonspecialentries)
{
  GtUword lastupdatecc = 0;
  GtUsainindextype *fillptr = sainseq->bucketfillptr;
#{declare_tmpvars(key)}
  GtSsainindextype *suftabptr, *bucketptr = NULL;

  for (suftabptr = suftab; suftabptr < suftab + nonspecialentries; suftabptr++)
  {
    GtSsainindextype position = *suftabptr;
    *suftabptr = ~position;
    if (position > 0)
    {
      GtUword currentcc;

      position--;
      currentcc = #{getc_call(key,"position")};
      if (currentcc < sainseq->numofchars)
      {
        gt_assert(currentcc > 0);
        GT_SAINUPDATEBUCKETPTR(currentcc);
        gt_assert(bucketptr != NULL && suftabptr < bucketptr);
        *bucketptr++ = (position > 0 &&
                        (#{getc_call(key,"position-1")}) < currentcc)
                        ? ~position : position;
#ifdef SAINSHOWSTATE
        gt_assert(bucketptr != NULL);
        printf("L-induce: suftab[" GT_WU "]=" GT_WD "\\n",
               (GtUword) (bucketptr-1-suftab),*(bucketptr-1));
#endif
      }
    }
  }
}

static void gt_sain_#{key}_induceStypesuffixes2(const GtSainseq *sainseq,
                                                 #{getc_param(key)},
                                         GtSsainindextype *suftab,
                                         GtUword nonspecialentries)
{
  GtUword lastupdatecc = 0;
  GtUsainindextype *fillptr = sainseq->bucketfillptr;
#{declare_tmpvars(key)}
  GtSsainindextype *suftabptr, *bucketptr = NULL;

  gt_sain_special_singleSinduction2(sainseq,
                                    suftab,
                                    (GtSsainindextype) sainseq->totallength,
                                    nonspecialentries);
  if (sainseq->seqtype == GT_SAIN_ENCSEQ ||
      sainseq->seqtype == GT_SAIN_BARE_ENCSEQ)
  {
    gt_sain_induceStypes2fromspecialranges(sainseq,suftab,nonspecialentries);
  }
  for (suftabptr = suftab + nonspecialentries - 1; suftabptr >= suftab;
       suftabptr--)
  {
    GtSsainindextype position;
    if ((position = *suftabptr) > 0)
    {
      GtUword currentcc;

      position--;
      currentcc = #{getc_call(key,"position")};
      if (currentcc < sainseq->numofchars)
      {
        GT_SAINUPDATEBUCKETPTR(currentcc);
        gt_assert(bucketptr != NULL && bucketptr - 1 < suftabptr);
        *(--bucketptr) = (position == 0 ||
                          (#{getc_call(key,"position-1")}) > currentcc)
                         ? ~position : position;
#ifdef SAINSHOWSTATE
        gt_assert(bucketptr != NULL);
        printf("S-induce: suftab[" GT_WU "]=" GT_WD "\\n",
                (GtUword) (bucketptr-suftab),*bucketptr);
#endif
      }
    } else
    {
      *suftabptr = ~position;
    }
  }
}
CCODE
end
["PLAINSEQ","ENCSEQ","BARE_ENCSEQ","INTSEQ"].each do |key|
fo.puts <<CCODE
static int gt_sain_#{key}_compare_Sstarstrings(const GtSainseq *sainseq,
                                               #{getc_param(key)},
                                               GtUword start1,
                                               GtUword start2,
                                               GtUword len)
{
  GtUword end1 = start1 + len;

  gt_assert(start1 <= sainseq->totallength &&
            start2 <= sainseq->totallength &&
            start1 != start2);
  while (start1 < end1)
  {
    GtUword cc1, cc2;

    if (start1 == sainseq->totallength)
    {
      gt_assert(start1 > start2);
      return 1;
    }
    if (start2 == sainseq->totallength)
    {
      gt_assert(start1 < start2);
      return -1;
    }
    cc1 = #{getc_call(key,"start1")};#{special2unique(key,"cc1","start1")};
    cc2 = #{getc_call(key,"start2")};#{special2unique(key,"cc2","start2")};
    if (cc1 < cc2)
    {
      return -1;
    }
    if (cc1 > cc2)
    {
      return 1;
    }
    start1++;
    start2++;
  }
  return 0;
}
static void gt_sain_#{key}_expandorder2original(GtSainseq *sainseq,
                                                 #{getc_param(key)},
                                         GtUword numberofsuffixes,
                                         GtUsainindextype *suftab)
{
  GtUsainindextype *suftabptr,
                   position,
                   *sstarsuffixes = suftab + GT_MULT2(numberofsuffixes),
                   *sstarfirstcharcount = NULL,
                   *bucketsize = NULL;
  GtUword nextcc = GT_UNIQUEINT(sainseq->totallength);
#{declare_tmpvars(key,true)}
  bool nextisStype = true;

  if (sainseq->seqtype == GT_SAIN_INTSEQ)
  {
    GtUword charidx;

    gt_assert(sainseq->sstarfirstcharcount == NULL);
    sstarfirstcharcount = sainseq->sstarfirstcharcount
                        = sainseq->bucketfillptr;
    bucketsize = sainseq->bucketsize;
    for (charidx = 0; charidx < sainseq->numofchars; charidx++)
    {
      sstarfirstcharcount[charidx] = 0;
      bucketsize[charidx] = 0;
    }
  }
#{sstarfirstcharcount_bucketsize_assert_not_NULL(key,["INTSEQ"])}#{init_esr(key)}
  for (position = (GtUsainindextype) (sainseq->totallength-1); /* Nothing */;
       position--)
  {
    GtUword currentcc = #{getc_call(key,"position",true)};
    bool currentisStype = (currentcc < nextcc ||
                           (currentcc == nextcc && nextisStype)) ? true : false;
    if (!currentisStype && nextisStype)
    {
#{sstarfirstcharcount_update(key,false,["INTSEQ"])}
      *--sstarsuffixes = position+1;
    }
#{bucketsize_update(key,["INTSEQ"])}
    nextisStype = currentisStype;
    nextcc = currentcc;
    if (position == 0)
    {
      break;
    }
  }
#{delete_esr(key)}
  for (suftabptr = suftab; suftabptr < suftab + numberofsuffixes; suftabptr++)
  {
    *suftabptr = sstarsuffixes[*suftabptr];
  }
}

static GtUword gt_sain_#{key}_insertSstarsuffixes(GtSainseq *sainseq,
                                                 #{getc_param(key)},
                                                 GtUsainindextype *suftab,
                                                 GtLogger *logger)
{
  GtUword nextcc = GT_UNIQUEINT(sainseq->totallength),
          countSstartype = 0;
  GtUsainindextype position, *fillptr = sainseq->bucketfillptr;
  GtSainbuffer *sainbuffer = gt_sainbuffer_new(suftab,fillptr,
                                               sainseq->numofchars,
                                               sainseq->totallength,logger);
#{declare_tmpvars(key,true)}
  bool nextisStype = true;

  gt_sain_endbuckets(sainseq);
#{init_esr(key)}
  for (position = (GtUsainindextype) (sainseq->totallength-1); /* Nothing */;
       position--)
  {
    GtUword currentcc = #{getc_call(key,"position",true)};
    bool currentisStype = (currentcc < nextcc ||
                           (currentcc == nextcc && nextisStype)) ? true : false;
    if (!currentisStype && nextisStype)
    {
      countSstartype++;
#{sstarfirstcharcount_update(key,true,["PLAINSEQ","ENCSEQ","BARE_ENCSEQ"])}
      if (sainbuffer != NULL)
      {
        gt_sainbuffer_update(sainbuffer,nextcc,position);
      } else
      {
        suftab[--fillptr[nextcc]] = position;
      }
#undef SAINSHOWSTATE
#ifdef SAINSHOWSTATE
      printf("Sstar.suftab[" GT_WU "]=" GT_WU "\\n",fillptr[nextcc],position+1);
#endif
    }
    nextisStype = currentisStype;
    nextcc = currentcc;
    if (position == 0)
    {
      break;
    }
  }
#{delete_esr(key)}
  gt_sainbuffer_flushall(sainbuffer);
  gt_sainbuffer_delete(sainbuffer);
  gt_assert(GT_MULT2(countSstartype) <= sainseq->totallength);
  return countSstartype;
}

static void gt_sain_#{key}_assignSstarlength(GtSainseq *sainseq,
                                             #{getc_param(key)},
                                             GtUsainindextype *lentab)
{
  bool nextisStype = true;
  GtUsainindextype position,
                   nextSstartypepos = (GtUsainindextype) sainseq->totallength;
#{declare_tmpvars(key,true)}
  GtUword nextcc = GT_UNIQUEINT(sainseq->totallength);
#{init_esr(key)}
  for (position = (GtUsainindextype) (sainseq->totallength-1); /* Nothing */;
       position--)
  {
    GtUword currentcc = #{getc_call(key,"position",true)};
    bool currentisStype = (currentcc < nextcc ||
                           (currentcc == nextcc && nextisStype)) ? true : false;
    if (!currentisStype && nextisStype)
    {
      gt_assert(position < nextSstartypepos);
      lentab[GT_DIV2(position+1)] = nextSstartypepos - position;
      nextSstartypepos = position + 1;
    }
    nextisStype = currentisStype;
    nextcc = currentcc;
    if (position == 0)
    {
      break;
    }
  }
#{delete_esr(key)}
}
CCODE
end
