#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <stdbool.h>
#include <mm_malloc.h>
#include <errno.h>
#define _GNU_SOURCE
#include <string.h>
#include <limits.h>
#include "profile.h"

/* TODO:
 *       - Profile length is zero based. We therefore added an extra line to account for it.
 *         Nevertheless, no memory space checking is so far implemented.
 *         So potentially we may completely screw up upon incorrect profile and segfault!
 */

#define SUB_COMMAND_MAX_SIZE 16

/*
 * This structure is fed by the Line Analyzer which parses the input, transforms delimiting symbols
 * into '\0' end character and sets the starting pointer within the ProfileLine structure.
 */
struct ProfileLine { 
  char * command;
  char * subcommand;
  char * keywords[32];
  char * values[32];
  size_t counter;
  _Bool isMatrix;
};

static char previousSubCommand[SUB_COMMAND_MAX_SIZE];

/*
 * Reads a line of profile and corrects the ending with a '\0' character.
 */
static inline void GetLine(FILE * const stream, char * const restrict Destination, const size_t MaxLength)
{
  if (fgets(Destination, MaxLength, stream) != NULL) {
    if (Destination[strlen(Destination)-1] == '\n') Destination[strlen(Destination)-1] = '\0';
  }
}

/*
 * Move to the first non space or tab character while within memory up to MaxMempoint.
 */
static inline char * FirstChar(const char * restrict Line, const uintptr_t MaxMemPoint)
{
  char * pos = (char*) Line; 
  while ( (*pos == ' ') || (*pos == '\t') ) {
    if ( (uintptr_t) ++pos == MaxMemPoint) break;
  }
  return pos;
}

/*
 * Copy the string Line into Destination until a given symbol is found. Max length applies.
 */
static inline char * CopyUptoSymbol(char * const restrict Destination, const char * restrict Line, const char Symbol, const size_t MaxLength)
{
  // WARNING: Destination should be cleared before since no '\0' will be added.

  for (size_t i=0; i<MaxLength; i++) {
    Destination[i] = Line[i];
    if ( Line[i] == Symbol){
      return (char*) &Line[i+1];
    }
  }
  return NULL;
}

static inline char * CopySubCommand(char * const restrict Destination, char * restrict Line, const char Symbol, const uintptr_t MaxLength)
{
#ifdef _DEBUG_VERBOSE_
  fputs(" COPY_SUB_COMMAND : '",stdout);
#endif
  for (size_t i=0; i<SUB_COMMAND_MAX_SIZE-1; ++i) {
    if ((uintptr_t) &Line[i] > MaxLength) break;

    if ( Line[i] == Symbol){
      Destination[i] = '\0';
      Line[i] = '\0';
#ifdef _DEBUG_VERBOSE_
      puts("'");
#endif
      return (char*) &Line[i+1];
    }
    Destination[i] = Line[i];
#ifdef _DEBUG_VERBOSE_
    fputc((int) Line[i], stdout);
#endif
  }
  return NULL;
}
/*
 * Trim the string from the right
 */
static inline void CleanEndString(char * const restrict String, const size_t Length)
{
  char * pos = &String[Length-1];
  while ( pos >= String && *pos == ' ') *pos-- = '\0';
}

/*
 * Move up to symbol while staying within memory limit.
 */
static inline char * GoUptoSymbol(char * const restrict String, const char Symbol, const uintptr_t MaxMemPoint)
{
  char * pos = String;
  while ( (uintptr_t) pos <= MaxMemPoint && *pos != Symbol) ++pos;
  if (*pos == Symbol) {
    *pos = '\0';
    ++pos;
  }
  return pos;
}

/*
 * Count and replace symbol with '\0' character within given string. Overall is bounded by a maximum memory position
 * Pay attention that not all count will give correct amount of tags. Indeed commas are n-1 tags.
 */
static inline size_t CountAndReplaceSymbol(char * const restrict String, const char Symbol, const uintptr_t MaxMemPoint)
{
  char * pos = String;
#ifdef _DEBUG_VERBOSE_
  fputs("CountAndReplaceSymbol : ",stdout); fflush(stdout); int c=0;
  while ( (uintptr_t) pos <= MaxMemPoint ) {
    printf("%i @0x%16.16x of 0x%16.16x\t", c++, (uintptr_t) pos, MaxMemPoint);
    fputc((int) *pos,stdout);
    fputc((int) '\n',stdout);
    pos++;
    fflush(stdout);
  }
  fputs("\n", stdout);
  fputs("CountAndReplaceSymbol : ",stdout);fflush(stdout);
#endif
  pos = String;
  size_t count = 0;
  while ( (uintptr_t) pos <= MaxMemPoint ) {
    if (*pos == Symbol) {
      ++count;
#ifdef _DEBUG_VERBOSE_
      fputs("\e[31;40m",stdout);
      fputc((int) *pos,stdout);
      fputs("\e[0m",stdout);
#endif
      *pos = '\0';
    }
#ifdef _DEBUG_VERBOSE_
    else {
      fputc((int) *pos,stdout);
    } 
#endif
    ++pos;
  }
#ifdef _DEBUG_VERBOSE_
  printf("\t count = %lu\n", count);
#endif
  return count;
}

/*
 * Profile Line parser.
 */
static int AnalyzeLine(char * restrict currentLine, struct ProfileLine * const restrict prfLine, _Bool * restrict MultipleLine)
{
  const size_t LineSize       = strlen(currentLine);
  const uintptr_t MaxMemPoint = (uintptr_t) &currentLine[LineSize-1];
#ifdef _VERBOSE_
  printf("\e[31;40mLine : %s\e[0m\n", currentLine);
#endif
  /* Set Multiple line to false */
  *MultipleLine = false;
  
  /* Clear result first */
  memset(prfLine, 0, sizeof(struct ProfileLine)-SUB_COMMAND_MAX_SIZE);

  /* Get Line command */
  char * Position  = FirstChar(currentLine, MaxMemPoint);
  prfLine->command = Position;
  const _Bool isMatrix = (Position[0] == 'M' && Position[1] == 'A') ? true : false;
  prfLine->isMatrix = isMatrix;
  
  /* Get to end of command and replace space by '\0' */
  while ( *Position != ' ' && (uintptr_t)Position <= MaxMemPoint) ++Position;
  while ( *Position == ' ' && (uintptr_t)Position <= MaxMemPoint) *Position++ = '\0';
  if ((uintptr_t) Position == MaxMemPoint) return 1;
#ifdef _VERBOSE_
  printf("\t'%s'", prfLine->command);
#endif
  if (!isMatrix) {
    prfLine->subcommand = Position;
    for (size_t i=0; i<SUB_COMMAND_MAX_SIZE;++i) previousSubCommand[i] = '\0';
    CleanEndString(currentLine, LineSize);
#ifdef _VERBOSE_
    printf("\t\t'%s'\n", prfLine->subcommand);
#endif
  } else {
    if (*Position == '/') {
      prfLine->subcommand = ++Position;
      Position = CopySubCommand(previousSubCommand, Position, ':', MaxMemPoint);
      //Position = GoUptoSymbol(Position, ':', MaxMemPoint);
    } else {
#ifdef _VERBOSE_
    printf(" \e[31;40mMULTIPLE MA LINES DETECTED\e[0m");
#endif 
      prfLine->subcommand = previousSubCommand;
      *MultipleLine = true;
    }
#ifdef _VERBOSE_
    printf("\t\t'%s'\n", prfLine->subcommand);
#endif
    //if ((uintptr_t)Position >= MaxMemPoint) return 1; causes bug when line is /MA M:
    // in any case not needed
    const size_t counter = CountAndReplaceSymbol(Position, ';', MaxMemPoint);
#ifdef _VERBOSE_
    printf("\t\t%lu tags found\n", counter);
#endif
    if (counter > 32) return 2;
    for (size_t i=0; i<counter; ++i) {
      /* TODO: Avoid checking from the beginning */
        char * tpos = GoUptoSymbol(Position, '=', MaxMemPoint);
        if ((uintptr_t) tpos >= MaxMemPoint) {
          fputs("\nError no = sign detected\n", stderr);
          //return 3;
        } else {
          prfLine->values[i] = tpos;
	  /* get back to = symbol */
// 	  while ( *tpos != '\0' && tpos > Position) --tpos;
	  const char * EqualPosition = tpos - 1;
	  /* get back to ; symbol */
          tpos -= 2;
          while ( *tpos != '\0' && tpos >= Position) --tpos;
	  ++tpos;
	  /* trim early space */
	  while ( *tpos == ' ' && tpos < EqualPosition) ++tpos;
          prfLine->keywords[i] = tpos;
        }
#ifdef _VERBOSE_
#ifdef _DEBUG_VERBOSE_
	printf("\t\t\t tag:'%s' @ 0x%8.8x \t\t value:'%s' @ 0x%8.8x\n", prfLine->keywords[i],prfLine->keywords[i], prfLine->values[i], prfLine->values[i]);
#else
        printf("\t\t\t tag:'%s' \t\t value:'%s'\n", prfLine->keywords[i], prfLine->values[i] );
#endif
#endif
    }
    prfLine->counter = counter;
  }
  return 0;
}

/*
 * The following functions read the score(s) from a given string and set the corresponding
 * destination(s) with the short integer (16 bit) values read.
 * Upon error, errno should hold the operating POSIX error.
 */
static inline size_t ReadScore(const char * const restrict String, short int * const restrict Score)
{
  errno = 0;
  if (String[0] == '*') {
    *Score = NLOW;
    return 0;
  } else {
    const long int Lscore =  strtol(String, NULL, 10);
    if (Lscore < SHRT_MIN || Lscore > SHRT_MAX) {
	fprintf(stderr, "Profile contains out of bound values for short int : %li\n", Lscore);
	exit(1);
    }
    *Score = (short int) Lscore;
    return errno;
  }
}

static inline size_t ReadScores(const char * const restrict String, short int * const restrict Scores, const size_t count)
{
  const char * restrict pos = String;

  errno = 0;
  if (*pos == '\'') ++pos;
  for (size_t i=0; i<count; ++i) {
    if (*pos == '*') {
      Scores[i] = NLOW;
      pos += 2;
    } else {
      char * newpos;
      const long int Lscore =  strtol(pos, &newpos, 10);
      if (Lscore < SHRT_MIN || Lscore > SHRT_MAX) {
	  fprintf(stderr, "Profile contains out of bound values for short int : %li\n", Lscore);
	  exit(1);
      }
      Scores[i] = (short int) Lscore;
      if (errno == 0) {
        while(*newpos != '\0') ++newpos;
        pos = (const char*) newpos + 1;
      } else {
        return 1;
      }
    }
//     if ( count > 1) fprintf(stderr, "%4i ", Scores[i]);
  }
//   if ( count > 1) fputs("\n", stderr);
  return 0;
}

/*
 * Exported functions to work on profile structure.
 */
int ReadProfile(const char * const restrict FileName, struct Profile * const prf)
{
  short int IIPD_ALPHABET       [           ALPHABET_SIZE+2] __attribute__((aligned(16)));
  short int IIPD_BOUNDARIES     [ INSERTION_BOUNDARIES_SIZE] __attribute__((aligned(16)));
  short int IMPD_ALPHABET       [           ALPHABET_SIZE+2] __attribute__((aligned(16)));
  TransitionScores IIPD_TRANSITIONS __attribute__((aligned(16)));
  
  char currentLine[PROFILE_MAX_LINE_SIZE] __attribute__((aligned(16)));
  
  struct ProfileLine AnalyzedLine;

  union Scores DefaultScores = { 
    Insertion : { IIPD_ALPHABET,
		  IIPD_BOUNDARIES,
		  &IIPD_TRANSITIONS,
		  0,
		  0,
		  0,
		  IMPD_ALPHABET }
  };
  union Scores WorkingScores;
  
  size_t Line=0, Alphabet_Length=0, Length=0;
  int InsertionCounter=-1, MatchCounter=0;
  int res;
  _Bool LZCO = false;
  _Bool MultipleLine;

  char DefaultMatchSymbol;
  char DefaultInsertionSymbol;
 
  /*
   * Try to open the file, upon failure emmit error
   */
  FILE* prfStream = fopen(FileName,"r");
  if (prfStream == NULL) {
    return 1;
  }

  /*
   * Clean Profile structure
   */
  memset(prf, 0, sizeof(struct Profile));
  
  /*
   * Initialize position-independent profile parameters
   *
   *   - general specification
   */
  
  prf->isCircular = false;
  prf->Length   = 0;

  /*   - disjoint mode */
  prf->DisjointData.MDIS = 1;
  strcpy(prf->DisjointData.CDIS[0], "UNIQUE\0");
  strcpy(prf->DisjointData.CDIS[1], "PROTECT\0");
  prf->DisjointData.JDIP[0] = 0;
  prf->DisjointData.JDIP[1] = 2;
  

  /*   - normalization modes */
  prf->NormalizationData.JNOR = 0;
  strcpy(prf->NormalizationData.CNOR[0], "LINEAR\0");
  strcpy(prf->NormalizationData.CNOR[1], "GLE_ZSCORE\0");
  strcpy(prf->NormalizationData.CNOR[2], "GLE_ZSCAVE\0");
  prf->NormalizationData.JNOP[0] = 2;
  prf->NormalizationData.JNOP[1] = 5;
  prf->NormalizationData.JNOP[2] = 5;

  for (int i=0; i<MAXN; ++i) {
      prf->NormalizationData.Values[i].NNOR = i;
      prf->NormalizationData.Values[i].NNPR = i;
  }

  /*   - cut-off */
  prf->CutOffData.JCUT = 0;
  
  InitializeDefault(&DefaultScores, &DefaultMatchSymbol, &DefaultInsertionSymbol);
  
  memset(previousSubCommand, 0, sizeof(char)*SUB_COMMAND_MAX_SIZE);
  /*
   * Read profile
   */
  while (!feof(prfStream)) {   
    GetLine(prfStream, currentLine, 512); ++Line;
#ifdef _VERBOSE_
    printf("\e[33;40m\t Match: %i\t\tInsertion: %i\e[0m\n", MatchCounter, InsertionCounter);
#endif

    if ((res=AnalyzeLine(currentLine, &AnalyzedLine, &MultipleLine)) != 0) {
      fprintf(stderr,"Error %i in analysis at line %lu\n", res, Line);
      return 1;
    }
    
    /* Header or matrix */
    if (!AnalyzedLine.isMatrix)
    {
       if ( (AnalyzedLine.command[0] == 'I' && AnalyzedLine.command[1] == 'D' ) ) {
         char * tpos = CopyUptoSymbol(prf->Identification, AnalyzedLine.subcommand, ';', 64);
         if (strstr(tpos, "MATRIX") == NULL) goto MissingMatrix ;
       } else if ( (AnalyzedLine.command[0] == 'A' && AnalyzedLine.command[1] == 'C' ) ) {
          CopyUptoSymbol(prf->AC_Number, AnalyzedLine.subcommand, ';', 64);
       } else if ( (AnalyzedLine.command[0] == 'D' && AnalyzedLine.command[1] == 'T' ) ) {
          strncpy(prf->Date, AnalyzedLine.subcommand, 127);
       } else if ( (AnalyzedLine.command[0] == 'D' && AnalyzedLine.command[1] == 'E' ) ) {
          strncpy(prf->Description, AnalyzedLine.subcommand, 255);
       }
    }
    else
    {
      /* ----------------------- GENERAL SPECIFICATIONS -------------------------------*/
      if (strcmp(AnalyzedLine.subcommand, "GENERAL_SPEC")==0) {
        for (size_t keys=0; keys<AnalyzedLine.counter;++keys) {
          if        (strcmp(AnalyzedLine.keywords[keys], "ALPHABET")==0) {
            Alphabet_Length = strlen(AnalyzedLine.values[keys]) - 2;
            if ( Alphabet_Length > ALPHABET_SIZE ) goto AlphabetSizeTooLarge;

            /* Map all character to unknown set as 0 */
            memset (prf->Alphabet_Mapping, 0, (ALPHABET_SIZE+1)*sizeof(char));
            memset (prf->CABC, 'X', (ALPHABET_SIZE+1)*sizeof(char));
	    prf->CABC[ALPHABET_SIZE+1] = '\0';

            // Update the mapping
            for (size_t i=1; i<=Alphabet_Length; ++i) {
              prf->CABC[i] = AnalyzedLine.values[keys][i];
              const size_t index = (size_t) ((unsigned char) AnalyzedLine.values[keys][i] - (unsigned char) 'A');
              prf->Alphabet_Mapping[index] = (unsigned char) i;
            }

            Alphabet_Length = ALPHABET_SIZE;
            prf->Alphabet_Length = Alphabet_Length;
            
          } else if (strcmp(AnalyzedLine.keywords[keys], "LENGTH")==0) {
            Length = (size_t) atoi(AnalyzedLine.values[keys]);
            prf->Length = Length;
            
            // Allocates memory
            //if ( AllocateScores(&DefaultScores, Alphabet_Length, Length) != 0 ) goto AllocationError; THIS IS IN THE STACK
            if ( AllocateScores(&WorkingScores, Alphabet_Length, Length) != 0 ) goto AllocationError;

            /* Copy score pointers to profile */
            memcpy(&(prf->Scores), &WorkingScores, sizeof(union Scores));

          } else if (strcmp(AnalyzedLine.keywords[keys], "TOPOLOGY")==0) {

          } else if (strcmp(AnalyzedLine.keywords[keys], "LOG_BASE")==0) {

          } else if (strcmp(AnalyzedLine.keywords[keys], "P0")==0) {

          } else if (strcmp(AnalyzedLine.keywords[keys], "P")==0) {

          }
        }
      }
      /* -----------------------      DISJOINT          -------------------------------*/
      else if (strcmp(AnalyzedLine.subcommand, "DISJOINT")==0) {
        SDisjoint * const djt = &(prf->DisjointData);
        /* Set some default in case data is missing */
        djt->NDIP[0] = 1;
        djt->NDIP[1] = Length;
        for (size_t keys=0; keys<AnalyzedLine.counter;++keys) {
          if        (strcmp(AnalyzedLine.keywords[keys], "DEFINITION")==0) {
            const char * const tval = AnalyzedLine.values[keys];
            for (size_t i=0; i<KDIS; ++i) {
              if ( strcmp(tval, djt->CDIS[i])==0) djt->MDIS = (int) i;
            }
          } else if (AnalyzedLine.keywords[keys][0] == 'N' && AnalyzedLine.keywords[keys][1] == '1' ) {
            djt->NDIP[0] = atoi(AnalyzedLine.values[keys]);
          } else if (AnalyzedLine.keywords[keys][0] == 'N' && AnalyzedLine.keywords[keys][1] == '2' ) {
            djt->NDIP[1] = atoi(AnalyzedLine.values[keys]);
          }
        }
      }
      /* -----------------------   NORMALIZATION        -------------------------------*/
      else if (strcmp(AnalyzedLine.subcommand, "NORMALIZATION")==0) {
        SNormalization * const nrm = &(prf->NormalizationData);
        register const size_t JNOR = nrm->JNOR;
        if (JNOR >= MAXN) goto TooManyNormalization;
	register SNormalizationItem * const nrmItem = &(nrm->Values[JNOR]);
        nrmItem->CNTX[0] = ' ';
        
        for (size_t keys=0; keys<AnalyzedLine.counter;++keys) {
          if        (strcmp(AnalyzedLine.keywords[keys], "FUNCTION")==0) {
            char ctmp[] = "GRIBSKOV";
            if (strcmp(AnalyzedLine.values[keys], "GRIBSKOV") == 0)
              AnalyzedLine.values[keys] = ctmp;

            int index = -1;
            for (int i=0; i<KNOR; ++i) {
              if (strcmp(AnalyzedLine.values[keys], nrm->CNOR[i])==0) index = i;
            }
            if ( index < 0 ) goto NormalizationError;
            nrmItem->MNOR = index;

          } else if (strcmp(AnalyzedLine.keywords[keys], "MODE")==0) {
            nrmItem->NNOR = atoi(AnalyzedLine.values[keys]);
          } else if (strcmp(AnalyzedLine.keywords[keys], "PRIORITY")==0) {
            nrmItem->NNPR = atoi(AnalyzedLine.values[keys]);
          } else if (strcmp(AnalyzedLine.keywords[keys], "TEXT")==0) {
            strncpy(nrmItem->CNTX, AnalyzedLine.values[keys], 32);
          } else {
            if ( AnalyzedLine.keywords[keys][0] == 'R') {
              // get the number
              const size_t index = (size_t) ( (unsigned char) AnalyzedLine.keywords[keys][1] - (unsigned char) '1');
	      if (index >= 5) {
		fprintf(stderr, "Normalization R index out of bound (%lu).\n", index);
		return 1;
	      }
              nrmItem->RNOP[index] = (float) atof(AnalyzedLine.values[keys]);
            }
          }
        }
        nrm->JNOR = JNOR + 1;
      }
      /* -----------------------        CUTOFF          -------------------------------*/
      else if (strcmp(AnalyzedLine.subcommand, "CUT_OFF")==0) {
        register SCutOff * const ct = &(prf->CutOffData);
        register const size_t JCUT = (size_t) ct->JCUT;
        if (JCUT >= MAXC) goto TooManyCutOff;
	register SCutOffItem * const restrict ctItem = &(ct->Values[JCUT]);

        for (size_t keys=0; keys<AnalyzedLine.counter;++keys) {
          if        (strcmp(AnalyzedLine.keywords[keys], "LEVEL")==0) {
            register const int itmp = atoi(AnalyzedLine.values[keys]);
            ctItem->MCLE = itmp;
            if (itmp == 0 ) LZCO = true;
          } else if (strcmp(AnalyzedLine.keywords[keys], "SCORE")==0) {
            ctItem->ICUT = atoi(AnalyzedLine.values[keys]);
          } else if (strcmp(AnalyzedLine.keywords[keys], "H_SCORE")==0) {
            ctItem->HCUT = (unsigned int) atoi(AnalyzedLine.values[keys]);
          } else if (strcmp(AnalyzedLine.keywords[keys], "TEXT")==0) {
            strncpy(ctItem->CCUT, AnalyzedLine.values[keys], 32);
          } else if (strcmp(AnalyzedLine.keywords[keys], "N_SCORE")==0) {
            const char * pos = AnalyzedLine.values[keys];
            const uintptr_t MaxMemory = (uintptr_t) pos + strlen(pos);
            const size_t count = 1 + CountAndReplaceSymbol(AnalyzedLine.values[keys], ',', MaxMemory);
            if (count > MAXN) goto TooManyScores;
            
            for (size_t i=0; i<count; ++i) {
              char * newpos;
              ctItem->RCUT[i] = strtof(pos, &newpos);
	      if (newpos == pos || newpos == 0) {
		fputs("Unable to read N_SCORE values\n" , stderr);
		return 1;
	      }
              while(*newpos != '\0') ++newpos;
              pos = newpos + 1; 
            }
            ctItem->JCNM = (int) count;
          } else if (strcmp(AnalyzedLine.keywords[keys], "MODE")==0) {
            const char * pos = AnalyzedLine.values[keys];
            const uintptr_t MaxMemory = (uintptr_t) pos + strlen(pos);
            const size_t count = 1 + CountAndReplaceSymbol(AnalyzedLine.values[keys], ',', MaxMemory);
            if (count > MAXN) goto TooManyModes;

            for (size_t i=0; i<count; ++i) {
              char * newpos;
              ctItem->MCUT[i] = (int) strtol(pos, &newpos, 10);
              while(*newpos != '\0') ++newpos;
              pos = newpos + 1;
            }
          }
        }
        ct->JCUT = JCUT + 1;
      }
      /* -----------------------       DEFAULT          -------------------------------*/
      else if (strcmp(AnalyzedLine.subcommand, "DEFAULT")==0) {
        if (AnalyzedLine.counter == 0) {
          InitializeDefault(&DefaultScores, &DefaultMatchSymbol, &DefaultInsertionSymbol);
        } else {
          for (size_t keys=0; keys<AnalyzedLine.counter;++keys) {
            const char * key = AnalyzedLine.keywords[keys];
	    if ( key[0] == 'S' && key[1] == 'Y') {
	      if (key[3] == 'M') {
		DefaultMatchSymbol = AnalyzedLine.values[keys][1];
	      } 
	      else if (key[3] == 'I') {
		DefaultInsertionSymbol = AnalyzedLine.values[keys][1];
	      }
	    }
            else if ( key[0] == 'M' && key[1] == '0' ) {
              if ( ReadScore(AnalyzedLine.values[keys], &(DefaultScores.Match.Alphabet[0])) != 0 ) goto ReadError;
            } else if ( key[0] == 'M' && key[1] == '\0' ) {
              const char * pos = AnalyzedLine.values[keys];
              const uintptr_t MaxMemory = (uintptr_t) pos + strlen(pos);
              const size_t count = CountAndReplaceSymbol(AnalyzedLine.values[keys], ',', MaxMemory) + 1;
              if (count == 1) {
                short int data;
                if ( ReadScore(AnalyzedLine.values[keys], &data) != 0 ) goto ReadError;
                for (size_t i=0; i<Alphabet_Length; ++i) DefaultScores.Match.Alphabet[i+1] = data;
              } else {
                if ( ReadScores(AnalyzedLine.values[keys], &(DefaultScores.Match.Alphabet[1]), count) != 0 ) goto ReadError;
              }
            } else if ( key[0] == 'D' && key[1] == '\0' ) {
              if ( ReadScore(AnalyzedLine.values[keys], &(DefaultScores.Match.Alphabet[Alphabet_Length+1])) != 0 ) goto ReadError;
            }
            else {
              short int *ptr;
              const size_t type = GetInsertionMemory(key, &DefaultScores.Insertion, &ptr);
              switch (type){
                case (0):
                  if ( ReadScore(AnalyzedLine.values[keys], ptr) != 0 ) goto ReadError;
                  break;
                case (1):
                  {
                    const char * pos = AnalyzedLine.values[keys];
                    const uintptr_t MaxMemory = (uintptr_t) pos + strlen(pos);
                    const size_t count = CountAndReplaceSymbol(AnalyzedLine.values[keys], ',', MaxMemory) + 1;
                    if (count == 1) {
                      short int data;
                      if ( ReadScore(AnalyzedLine.values[keys], &data) != 0 ) goto ReadError;
                      for (size_t i=0; i<Alphabet_Length; ++i) ptr[i] = data;
                    } else {
                      if ( ReadScores(AnalyzedLine.values[keys], ptr, count) != 0 ) goto ReadError;
                    }
                  }
                  break;
                default:
                  goto UnknownKey;
              }
            }
          }
        }
      }
      /* -----------------------        INSERTIONS      -------------------------------*/
      else if (AnalyzedLine.subcommand[0] == 'I') {
	if (MultipleLine) {
	  --InsertionCounter;
	  PreviousInsertionProfile(&WorkingScores.Insertion);
	} else {
	  /* Copy default values */
	  memcpy(WorkingScores.Insertion.Alphabet,    DefaultScores.Insertion.Alphabet,    (Alphabet_Length+2)*sizeof(short int));
	  memcpy(WorkingScores.Insertion.Boundaries,  DefaultScores.Insertion.Boundaries,  (INSERTION_BOUNDARIES_SIZE)*sizeof(short int));
	  memcpy(WorkingScores.Insertion.Transitions, DefaultScores.Insertion.Transitions, sizeof(TransitionScores));
	}
        /* Check whether there is an implicit /M:, if so insert it */
        if (MatchCounter < InsertionCounter + 1) {
//           fprintf(stderr, "Line %lu : implicit Match : %lu < %lu\n", Line, MatchCounter, InsertionCounter);
#ifdef _VERBOSE_
	  puts(" \e[0;32m IMPLICIT INSERTION OF M\e[0m\n");
#endif
          memcpy(WorkingScores.Match.Alphabet, DefaultScores.Match.Alphabet, (Alphabet_Length+2)*sizeof(short int));
          NextMatchProfile(&WorkingScores.Match);
          ++MatchCounter;
        }

        /* Read the scores */
        for (size_t keys=0; keys<AnalyzedLine.counter;++keys) {
          const char * key = AnalyzedLine.keywords[keys];
          short int *ptr;
          const size_t type = GetInsertionMemory(key, &WorkingScores.Insertion, &ptr);
          switch (type){
            case (0):
              if ( ReadScore(AnalyzedLine.values[keys], ptr) != 0 ) goto ReadError;
              break;
            case (1):
              {
                const char * pos = AnalyzedLine.values[keys];
                const uintptr_t MaxMemory = (uintptr_t) pos + strlen(pos);
                const size_t count = CountAndReplaceSymbol(AnalyzedLine.values[keys], ',', MaxMemory) + 1;
                if (count == 1) {
                  short int data;
                  if ( ReadScore(AnalyzedLine.values[keys], &data) != 0 ) goto ReadError;
                  for (size_t i=0; i<Alphabet_Length; ++i) ptr[i] = data;
                } else {
                  if ( ReadScores(AnalyzedLine.values[keys], ptr, count) != 0 ) goto ReadError;
                }
              }
              break;
            default:
              goto UnknownKey;
          }
        }

        /* Increment I counter */
        NextInsertionProfile(&WorkingScores.Insertion);
        ++InsertionCounter;
      }
      /* -----------------------         MATCHES       --------------------------------*/
      else if (AnalyzedLine.subcommand[0] == 'M') {
	if (MultipleLine) {
	  --MatchCounter;
	  PreviousMatchProfile(&WorkingScores.Match);
	} else {
	  /* Copy default values */
	  memcpy(WorkingScores.Match.Alphabet, DefaultScores.Match.Alphabet, (Alphabet_Length+2)*sizeof(short int));
	}
        /* Check whether there is an implicit /I:, if so insert it */
        if (InsertionCounter < MatchCounter ) {
//           fprintf(stderr, "Line %lu : implicit Insertion : %lu <= %lu\n", Line, InsertionCounter, MatchCounter);
#ifdef _VERBOSE_
	  puts(" \e[0;32m IMPLICIT INSERTION OF I\e[0m\n");
#endif
	  memcpy(WorkingScores.Insertion.Alphabet,    DefaultScores.Insertion.Alphabet,    (Alphabet_Length+2)*sizeof(short int));
	  memcpy(WorkingScores.Insertion.Boundaries,  DefaultScores.Insertion.Boundaries,  (INSERTION_BOUNDARIES_SIZE)*sizeof(short int));
	  memcpy(WorkingScores.Insertion.Transitions, DefaultScores.Insertion.Transitions, sizeof(TransitionScores));

//        CopyPreviousInsertionProfile(&WorkingScores.Insertion);
	  
	  NextInsertionProfile(&WorkingScores.Insertion);
          ++InsertionCounter;
        }

        /* Read the scores */
        for (size_t keys=0; keys<AnalyzedLine.counter;++keys) {
          const char * key = AnalyzedLine.keywords[keys];
          if ( key[0] == 'M' && key[1] == '0' ) {
              if ( ReadScore(AnalyzedLine.values[keys], &(WorkingScores.Match.Alphabet[0])) != 0 ) goto ReadError;
          } else if ( key[0] == 'M' && key[1] == '\0' ) {
            const char * pos = AnalyzedLine.values[keys];
            const uintptr_t MaxMemory = (uintptr_t) pos + strlen(pos);
            const size_t count = CountAndReplaceSymbol(AnalyzedLine.values[keys], ',', MaxMemory) + 1;
            if (count == 1) {
              short int data;
              if ( ReadScore(AnalyzedLine.values[keys], &data) != 0 ) goto ReadError;
              for (size_t i=0; i<Alphabet_Length; ++i) WorkingScores.Match.Alphabet[i+1] = data;
            } else {
              if ( ReadScores(AnalyzedLine.values[keys], &(WorkingScores.Match.Alphabet[1]), count) != 0 ) goto ReadError;
            }
          } else if ( key[0] == 'D' && key[1] == '\0' ) {
            if ( ReadScore(AnalyzedLine.values[keys], &WorkingScores.Match.Alphabet[Alphabet_Length+1]) != 0 ) goto ReadError;
          }
        }

        /* Increment M counter */
        NextMatchProfile(&WorkingScores.Match);
        ++MatchCounter;
      }
      /* -----------------------       UNKNOWN          -------------------------------*/
      else {

      }
    }
  }
  
  /* Insert possible missing I when profile starts with M */
  if (InsertionCounter == Length-1 ) {
    /* Copy default values */
    memcpy(WorkingScores.Insertion.Alphabet, DefaultScores.Insertion.Alphabet, (Alphabet_Length+2)*sizeof(short int));
    memcpy(WorkingScores.Insertion.Boundaries,  DefaultScores.Insertion.Boundaries,  (INSERTION_BOUNDARIES_SIZE)*sizeof(short int));
    memcpy(WorkingScores.Insertion.Transitions, DefaultScores.Insertion.Transitions, sizeof(TransitionScores));
    ++ InsertionCounter;
  }

  /*
   * Operate on the Insertion Score matrix to place boundaries scores within the structure at
   * location reserved for them.
   * NOTE: This was initially the IIPX matrix filled up within pfsearch.
   */
  /*
     IIPX( XM,I1) = MAX(MLOW,IIPP( B1,I1) + IIPP( BM,I1))
     IIPX( XI,I1) = MAX(MLOW,IIPP( B1,I1) + IIPP( BI,I1))
     IIPX( XD,I1) = MAX(MLOW,IIPP( B1,I1) + IIPP( BD,I1))

     IIPX( YM,I1) = MAX(MLOW,IIPP( B0,I1) + IIPP( BM,I1))
     IIPX( YI,I1) = MAX(MLOW,IIPP( B0,I1) + IIPP( BI,I1))
     IIPX( YD,I1) = MAX(MLOW,IIPP( B0,I1) + IIPP( BD,I1))

     IIPX( MX,I1) = MAX(MLOW,IIPP( E1,I1) + IIPP( ME,I1))
     IIPX( IX,I1) = MAX(MLOW,IIPP( E1,I1) + IIPP( IE,I1))
     IIPX( DX,I1) = MAX(MLOW,IIPP( E1,I1) + IIPP( DE,I1))

     IIPX( MY,I1) = MAX(MLOW,IIPP( E0,I1) + IIPP( ME,I1))
     IIPX( IY,I1) = MAX(MLOW,IIPP( E0,I1) + IIPP( IE,I1))
     IIPX( DY,I1) = MAX(MLOW,IIPP( E0,I1) + IIPP( DE,I1))
  */
  {
    #define MAXMLOW(a) (short int) ( (a>MLOW) ? a : MLOW )
    #define CHECK_AND_SET(a,b,c) {\
      const int Lscore = ((int) b ) + ((int) c);\
      if (Lscore < SHRT_MIN || Lscore > SHRT_MAX) {\
	fprintf(stderr, "Profile contains out of bound values for short int : %i\n"\
	                " arising from the addition of %i with %i\n", Lscore, (int)b, (int)c);\
	exit(1);\
      } else {\
	a = (short int) ( (Lscore > MLOW) ? Lscore : MLOW );\
      }\
    }
    register const short int * restrict const InsertionBoundaries = prf->Scores.Insertion.Boundaries;
    register short int * restrict const InsertionScores           = prf->Scores.Insertion.Transitions->Element;
    register ScoreTuple * restrict const FirstScores              = prf->Scores.Insertion.FirstSequenceProtein;
    register ScoreTuple * restrict const LastScores               = prf->Scores.Insertion.LastSequenceProtein;
    register const short int MLOW = NLOW/4*3;

    if ( prf->isCircular) {
      for (size_t i=0; i<=Length; ++i) {
        register const size_t offset  = INSERTION_TRANSITIONS_SIZE*i;
        register const size_t Boffset = INSERTION_BOUNDARIES_SIZE*i;
/*
  WARNING: POTENTIAL ISSUE WITH _MX or _MY
  */
        CHECK_AND_SET(InsertionScores[offset + _XM], InsertionBoundaries[Boffset + _B1], InsertionBoundaries[Boffset + _BM]);
        CHECK_AND_SET(InsertionScores[offset + _XI], InsertionBoundaries[Boffset + _B1], InsertionBoundaries[Boffset + _BI]);
        CHECK_AND_SET(InsertionScores[offset + _XD], InsertionBoundaries[Boffset + _B1], InsertionBoundaries[Boffset + _BD]);
        
        // Minimize dummy element 
        InsertionScores[offset + _DUMMY] = NLOW;
        CHECK_AND_SET(InsertionScores[offset + _MX], InsertionBoundaries[Boffset + _E0], InsertionBoundaries[Boffset + _ME]);
        CHECK_AND_SET(InsertionScores[offset + _IX], InsertionBoundaries[Boffset + _E0], InsertionBoundaries[Boffset + _IE]);
        CHECK_AND_SET(InsertionScores[offset + _DX], InsertionBoundaries[Boffset + _E0], InsertionBoundaries[Boffset + _DE]);
       
	CHECK_AND_SET(FirstScores[i].To[MATCH]    , InsertionBoundaries[Boffset + _B0], InsertionBoundaries[Boffset + _BM]);
        CHECK_AND_SET(FirstScores[i].To[INSERTION], InsertionBoundaries[Boffset + _B0], InsertionBoundaries[Boffset + _BI]);
        CHECK_AND_SET(FirstScores[i].To[DELETION] , InsertionBoundaries[Boffset + _B0], InsertionBoundaries[Boffset + _BD]);

        CHECK_AND_SET(LastScores[i].From[MATCH]    , InsertionBoundaries[Boffset + _E1], InsertionBoundaries[Boffset + _ME]);
        CHECK_AND_SET(LastScores[i].From[INSERTION], InsertionBoundaries[Boffset + _E1], InsertionBoundaries[Boffset + _IE]);
        CHECK_AND_SET(LastScores[i].From[DELETION] , InsertionBoundaries[Boffset + _E1], InsertionBoundaries[Boffset + _DE]);
      }
    } else {
      const size_t NDIP1 = prf->DisjointData.NDIP[0];
      const size_t NDIP2 = prf->DisjointData.NDIP[1];
      
      for (size_t i=0; i<NDIP1; ++i) {
        register const size_t offset  = INSERTION_TRANSITIONS_SIZE*i;
        register const size_t Boffset = INSERTION_BOUNDARIES_SIZE*i;

        CHECK_AND_SET(InsertionScores[offset + _XM], InsertionBoundaries[Boffset + _B1], InsertionBoundaries[Boffset + _BM]);
        CHECK_AND_SET(InsertionScores[offset + _XI], InsertionBoundaries[Boffset + _B1], InsertionBoundaries[Boffset + _BI]);
        CHECK_AND_SET(InsertionScores[offset + _XD], InsertionBoundaries[Boffset + _B1], InsertionBoundaries[Boffset + _BD]);
        
        // Minimize dummy element 
        InsertionScores[offset + _DUMMY] = NLOW;
        CHECK_AND_SET(InsertionScores[offset + _MX], NLOW, InsertionBoundaries[Boffset + _ME]);
        CHECK_AND_SET(InsertionScores[offset + _IX], NLOW, InsertionBoundaries[Boffset + _IE]);
        CHECK_AND_SET(InsertionScores[offset + _DX], NLOW, InsertionBoundaries[Boffset + _DE]);

	CHECK_AND_SET(FirstScores[i].To[MATCH]    , InsertionBoundaries[Boffset + _B0], InsertionBoundaries[Boffset + _BM]);
        CHECK_AND_SET(FirstScores[i].To[INSERTION], InsertionBoundaries[Boffset + _B0], InsertionBoundaries[Boffset + _BI]);
        CHECK_AND_SET(FirstScores[i].To[DELETION] , InsertionBoundaries[Boffset + _B0], InsertionBoundaries[Boffset + _BD]);

        CHECK_AND_SET(LastScores[i].From[MATCH]    , (short int)NLOW, InsertionBoundaries[Boffset + _ME]);
        CHECK_AND_SET(LastScores[i].From[INSERTION], (short int)NLOW, InsertionBoundaries[Boffset + _IE]);
        CHECK_AND_SET(LastScores[i].From[DELETION] , (short int)NLOW, InsertionBoundaries[Boffset + _DE]);
      }
      for (size_t i=NDIP1; i<NDIP2; ++i) {
        register const size_t offset  = INSERTION_TRANSITIONS_SIZE*i;
        register const size_t Boffset = INSERTION_BOUNDARIES_SIZE*i;

        CHECK_AND_SET(InsertionScores[offset + _XM], InsertionBoundaries[Boffset + _B1], InsertionBoundaries[Boffset + _BM]);
        CHECK_AND_SET(InsertionScores[offset + _XI], InsertionBoundaries[Boffset + _B1], InsertionBoundaries[Boffset + _BI]);
        CHECK_AND_SET(InsertionScores[offset + _XD], InsertionBoundaries[Boffset + _B1], InsertionBoundaries[Boffset + _BD]);
        
        // Minimize dummy element 
        InsertionScores[offset + _DUMMY] = NLOW;
        CHECK_AND_SET(InsertionScores[offset + _MX], InsertionBoundaries[Boffset + _E1], InsertionBoundaries[Boffset + _ME]);
        CHECK_AND_SET(InsertionScores[offset + _IX], InsertionBoundaries[Boffset + _E1], InsertionBoundaries[Boffset + _IE]);
        CHECK_AND_SET(InsertionScores[offset + _DX], InsertionBoundaries[Boffset + _E1], InsertionBoundaries[Boffset + _DE]);

        CHECK_AND_SET(FirstScores[i].To[MATCH]    , InsertionBoundaries[Boffset + _B0], InsertionBoundaries[Boffset + _BM]);
        CHECK_AND_SET(FirstScores[i].To[INSERTION], InsertionBoundaries[Boffset + _B0], InsertionBoundaries[Boffset + _BI]);
        CHECK_AND_SET(FirstScores[i].To[DELETION] , InsertionBoundaries[Boffset + _B0], InsertionBoundaries[Boffset + _BD]);

        CHECK_AND_SET(LastScores[i].From[MATCH]    , InsertionBoundaries[Boffset + _E0], InsertionBoundaries[Boffset + _ME]);
        CHECK_AND_SET(LastScores[i].From[INSERTION], InsertionBoundaries[Boffset + _E0], InsertionBoundaries[Boffset + _IE]);
        CHECK_AND_SET(LastScores[i].From[DELETION] , InsertionBoundaries[Boffset + _E0], InsertionBoundaries[Boffset + _DE]);
      }
      for (size_t i=NDIP2; i<=Length; ++i) {
        register const size_t offset  = INSERTION_TRANSITIONS_SIZE*i;
        register const size_t Boffset = INSERTION_BOUNDARIES_SIZE*i;

        CHECK_AND_SET(InsertionScores[offset + _XM], NLOW, InsertionBoundaries[Boffset + _BM]);
        CHECK_AND_SET(InsertionScores[offset + _XI], NLOW, InsertionBoundaries[Boffset + _BI]);
        CHECK_AND_SET(InsertionScores[offset + _XD], NLOW, InsertionBoundaries[Boffset + _BD]);
        
        // Minimize dummy element 
        InsertionScores[offset + _DUMMY] = NLOW;
        CHECK_AND_SET(InsertionScores[offset + _MX], InsertionBoundaries[Boffset + _E1], InsertionBoundaries[Boffset + _ME]);
        CHECK_AND_SET(InsertionScores[offset + _IX], InsertionBoundaries[Boffset + _E1], InsertionBoundaries[Boffset + _IE]);
        CHECK_AND_SET(InsertionScores[offset + _DX], InsertionBoundaries[Boffset + _E1], InsertionBoundaries[Boffset + _DE]);

        CHECK_AND_SET(FirstScores[i].To[MATCH]    , (short int)NLOW, InsertionBoundaries[Boffset + _BM]);
        CHECK_AND_SET(FirstScores[i].To[INSERTION], (short int)NLOW, InsertionBoundaries[Boffset + _BI]);
        CHECK_AND_SET(FirstScores[i].To[DELETION] , (short int)NLOW, InsertionBoundaries[Boffset + _BD]);

        CHECK_AND_SET(LastScores[i].From[MATCH]    , InsertionBoundaries[Boffset + _E0], InsertionBoundaries[Boffset + _ME]);
        CHECK_AND_SET(LastScores[i].From[INSERTION], InsertionBoundaries[Boffset + _E0], InsertionBoundaries[Boffset + _IE]);
        CHECK_AND_SET(LastScores[i].From[DELETION] , InsertionBoundaries[Boffset + _E0], InsertionBoundaries[Boffset + _DE]);
      }
    }
    #undef CHECK_AND_SET
    #undef MAXMLOW
  }

  /* Insert possible missing M line at last */
  if (MatchCounter == Length) {
    /* Copy default values */
    memcpy(WorkingScores.Match.Alphabet, DefaultScores.Match.Alphabet, (Alphabet_Length+2)*sizeof(short int));
  }
  
  /* CHECK CONSISTENCY */
  if (MatchCounter != Length) {
     if ( MatchCounter < Length )
      fprintf(stderr, "There is not enough match lines %i <> %lu\n", MatchCounter, Length );
     else
      fprintf(stderr, "There is too many match lines with profile %i <> %lu\n", MatchCounter, Length );
     return 1;
  }
  if (InsertionCounter != Length) {
     if ( InsertionCounter < Length )
      fprintf(stderr, "There is not enough insertion lines %i <> %lu\n", InsertionCounter, Length );
     else
      fprintf(stderr, "There is too many insertion lines with profile %i <> %lu\n", InsertionCounter, Length );
     return 1;
  }

  if (LZCO != true || prf->CutOffData.JCUT == 0) {
      fprintf(stderr, "No level 0 CUT-OFF data block in profile.\n");
      return 1;
  }

  if (Length < 1) {
    fprintf(stderr, "Unexpected end of profile. Profile has zero length.\n");
    return 1;
  }

  if (prf->DisjointData.NDIP[0] <= 0 || prf->DisjointData.NDIP[0] > Length) {
    fprintf(stderr,
            "Warning: Disjointness parameter 1 (%i) out of bound. Parameter set to acceptable value.\n",
            prf->DisjointData.NDIP[0]);
    prf->DisjointData.NDIP[0] = 1;
  }
  if (prf->DisjointData.NDIP[1] <= 0 || prf->DisjointData.NDIP[1] > Length) {
    fprintf(stderr,
            "Warning: Disjointness parameter 2 (%i) out of bound. Parameter set to acceptable value.\n",
            prf->DisjointData.NDIP[1]);
    prf->DisjointData.NDIP[1] = Length;    
  }

  if (prf->DisjointData.NDIP[1] < prf->DisjointData.NDIP[0]) {
    const int tmp = prf->DisjointData.NDIP[1];
    prf->DisjointData.NDIP[1] = prf->DisjointData.NDIP[0];
    prf->DisjointData.NDIP[0] = tmp;
  }
  
  return 0;
  
  /*
   * ERRORS
   */
  
// MissingSymbol:
//    fprintf(stderr, "Missing symbol %c at line %lu of %s\n\tLine: %s\n",Symbol,Line, FileName, prfLine);
//    return 1;
   
MissingMatrix:
   if (prf->Identification[0] != '\0') {
    fprintf(stderr, "Missing MATRIX keyword at line %lu of profile %s\n\tLine: %s\n", Line, prf->Identification, currentLine);
   } else {
    fprintf(stderr, "Missing MATRIX keyword at line %lu of %s\n\tLine: %s\n", Line, FileName, currentLine);
   }
   return 1;
   
AlphabetSizeTooLarge:
   fprintf(stderr, "Alphabet size exceeds hard defined size: %u > %lu\n", ALPHABET_SIZE, prf->Alphabet_Length);
   return 1;

NormalizationError:
  fprintf(stderr, "Error within normalization section at line %lu of %s\nFUNCTION value matches none of the following.\n",
          Line, FileName);
  for (int i=0; i<KNOR; ++i) fprintf(stderr, "%s ", prf->NormalizationData.CNOR[i]);
  fputs("\n", stderr);
  
  return 1;
    
TooManyNormalization:
  fprintf(stderr, "Too many normalization parameters at line %lu of %s\n\tMaximum is %i.\n", Line, FileName, 0);
   return 1;

TooManyCutOff:
  fprintf(stderr, "Too many cutoffs parameters at line %lu of %s\n\tMaximum is %i.\n", Line, FileName, 0);
   return 1;

TooManyModes:
   fprintf(stderr, "Too many modes parameters at line %lu of %s\n\tMaximum is %i.\n", Line, FileName, 0);
   return 1;

TooManyScores:
   fprintf(stderr, "Too many scores at line %lu of %s\n\tMaximum is %i.\n", Line, FileName, 0);
   return 1;

ReadError:
   fprintf(stderr, "Error reading one parameter at line %lu of %s\n\tLine: %s\n", Line, FileName, currentLine);
   return 1;

UnknownKey:
   fprintf(stderr, "Unknown keyword found at line %lu of %s\n\tLine: %s\n", Line, FileName, currentLine);
   return 1;

AllocationError:
  fprintf(stderr, "Unable to allocate sufficient memory\n");
  return 1;
   
}

void FreeProfile(struct Profile * const prf)
{
  FreeScores(&(prf->Scores));
  memset(prf, 0, sizeof(struct Profile));
}

#ifdef _TEST
/* default value for header is 4 digits*/
// #define INT_FORMAT "%4i"
// #define NLOW_FORMAT "NLOW"
// #define MLOW_FORMAT "MLOW"
// #define SPACE "  "
//#define LINE "-----"

#define INT_FORMAT "%6i"
#define NLOW_FORMAT "  NLOW"
#define MLOW_FORMAT "  MLOW"
#define SPACE "    "
#define LINE "-------"
int main(int argc, char *argv[])
{
  struct Profile prf;

  if (argc < 2 ) {
     fputs("Give at least a profile file name\n" , stderr);
     return 1;
  }
  if (ReadProfile(argv[1], &prf) != 0) {
    FreeProfile(&prf);
    return 1;
  }
  if (argc > 2) return 0;
  struct SMatch Match= prf.Scores.Match;

  puts("Alphabet Mapping");
  for (size_t i=0; i<ALPHABET_SIZE; ++i) {
    printf("Map %c=%2u\t", (char) ((unsigned char) 'A' + (unsigned char) i), (unsigned int) prf.Alphabet_Mapping[i]);
    if ((i+1) % 8 == 0 ) puts("");
  }
  puts("\n");
  const size_t prfLength = prf.Length + 1;
  
  printf("Match matrix with alignment %lu\n\n",Match.AlignStep );
  printf("    | ");
  for (size_t alpha=0; alpha<prf.Alphabet_Length+2; ++alpha) {
    printf(SPACE "%2lu ", alpha);
  }
  fputs("\n", stdout);
  printf("    | ");
  for (size_t alpha=0; alpha<prf.Alphabet_Length+2; ++alpha) {
    fputs(LINE, stdout);
  }
  fputs("\n", stdout);
  
  for (size_t iprf=0; iprf<prfLength; ++iprf) {
    const short int * MatchLine = &Match.Alphabet[iprf*Match.AlignStep];
    printf("%3lu | ", iprf+1);
    for (size_t alpha=0; alpha<prf.Alphabet_Length+2; ++alpha) {
      if (MatchLine[alpha] == NLOW) {
        printf(NLOW_FORMAT " ");
      } else {
        printf(INT_FORMAT " ", MatchLine[alpha]);
      }
    }
    fputs("\n", stdout);
  }

//   puts("Transpose Match matrix");
//   const int * TIMatch = TransposeAndConvertMatchMatrix(&(prf.Scores.Match), prf.Alphabet_Length, prf.Length);
//   const int * MatchLine = TIMatch;
//   const size_t Aligned_Profile_Length = (prfLength+1 + 15) & ~15;
//   for (size_t alpha=0; alpha<prf.Alphabet_Length; ++alpha) {
//       printf("%3lu | ", alpha);
//       for (size_t iprf=0; iprf<prfLength; ++iprf) {
// 	if (MatchLine[iprf] == NLOW) {
// 	  printf("NLOW ");
// 	} else {
// 	  printf("%4i ", MatchLine[iprf]);
// 	}
//       }
//       fputs("\n", stdout);
//       MatchLine += Aligned_Profile_Length;
//   }
  
  struct SInsertion Insertion= prf.Scores.Insertion;
  printf("\nInsertion alphabet matrix with alignment %lu\n\n",Insertion.AlignStep );
  printf("    | ");
  for (size_t alpha=0; alpha<prf.Alphabet_Length+2; ++alpha) {
    printf(SPACE "%2lu ", alpha);
  }
  fputs("\n", stdout);
  printf("    | ");
  for (size_t alpha=0; alpha<prf.Alphabet_Length+2; ++alpha) {
    fputs(LINE, stdout);
  }
  fputs("\n", stdout);

  for (size_t iprf=0; iprf<prfLength; ++iprf) {
    short int * InsertionLine = &Insertion.Alphabet[iprf*Insertion.AlignStep];
    printf("%3lu | ", iprf+1);
    for (size_t alpha=0; alpha<prf.Alphabet_Length+2; ++alpha) {
      if (InsertionLine[alpha] == NLOW) {
        printf(NLOW_FORMAT " ");
      } else {
        printf(INT_FORMAT " ", InsertionLine[alpha]);
      }
    }
    fputs("\n", stdout);
  }

  printf("\nInsertion boundaries matrix with alignment %i\n\n", INSERTION_BOUNDARIES_SIZE );
  char Header[] = "     " SPACE "_B0" SPACE "_B1" SPACE "_E0" SPACE "_E1" SPACE "_BM" SPACE "_BI" SPACE "_BD" SPACE "_BE" SPACE "_ME" SPACE "_IE" SPACE "_DE\n";
  
  fputs(Header, stdout);
  fputs("    |", stdout);
  for (size_t i=0; i<strlen(Header)-5; ++i) fputc('-', stdout);
  fputs("\n", stdout);
  for (size_t iprf=0; iprf<prfLength; ++iprf) {
    short int * InsertionLine = &Insertion.Boundaries[iprf*INSERTION_BOUNDARIES_SIZE];
    printf("%3lu | ", iprf+1);
    for (size_t alpha=0; alpha<INSERTION_BOUNDARIES_SIZE; ++alpha) {
      if (InsertionLine[alpha] == NLOW) {
        printf(NLOW_FORMAT " ");
      } else {
        printf(INT_FORMAT " ", InsertionLine[alpha]);
      }
    }
    fputs("\n", stdout);
  }


  const TransitionScores * restrict InsertionLine = prf.Scores.Insertion.Transitions;
  const ScoreTuple * const restrict FirstSeq      = prf.Scores.Insertion.FirstSequenceProtein;
  const ScoreTuple * const restrict LastSeq       = prf.Scores.Insertion.LastSequenceProtein;
  
#define PRINT_VALUE(x) { \
  if (x == NLOW) {\
    printf(NLOW_FORMAT " ");\
  } else if ( x == NLOW/4*3) {\
    printf(MLOW_FORMAT " ");\
  } else {\
    printf(INT_FORMAT " ", x);\
  }\
}
  
  printf("\nInsertion transition matrix with alignment %i\n\n", INSERTION_TRANSITIONS_SIZE );
  char Header2[] = "     " SPACE "_XM" SPACE "_MM" SPACE "_IM" SPACE "_DM" SPACE "_XI" SPACE "_MI" SPACE "_II" SPACE "_DI" SPACE "_XD" SPACE "_MD" SPACE "_ID" SPACE "_DD"\
                   "  " SPACE "_MX" SPACE "_IX" SPACE "_DX"\
                   "  " SPACE "_MY" SPACE "_IY" SPACE "_DY"\
                   "  " SPACE "_YM" SPACE "_YI" SPACE "_YD\n";
  fputs(Header2, stdout);
  fputs("    |", stdout);
  for (size_t i=0; i<strlen(Header2)-5; ++i) fputc('-', stdout);
  fputs("\n", stdout);

  for (size_t iprf=0; iprf<prfLength; ++iprf) {
    printf("%3lu | ", iprf+1);
    PRINT_VALUE(InsertionLine[iprf].From[EXTRA].To[MATCH]);
    PRINT_VALUE(InsertionLine[iprf].From[MATCH].To[MATCH]);
    PRINT_VALUE(InsertionLine[iprf].From[INSERTION].To[MATCH]);
    PRINT_VALUE(InsertionLine[iprf].From[DELETION].To[MATCH]);
    
    PRINT_VALUE(InsertionLine[iprf].From[EXTRA].To[INSERTION]);
    PRINT_VALUE(InsertionLine[iprf].From[MATCH].To[INSERTION]);
    PRINT_VALUE(InsertionLine[iprf].From[INSERTION].To[INSERTION]);
    PRINT_VALUE(InsertionLine[iprf].From[DELETION].To[INSERTION]);
    
    PRINT_VALUE(InsertionLine[iprf].From[EXTRA].To[DELETION]);
    PRINT_VALUE(InsertionLine[iprf].From[MATCH].To[DELETION]);
    PRINT_VALUE(InsertionLine[iprf].From[INSERTION].To[DELETION]);
    PRINT_VALUE(InsertionLine[iprf].From[DELETION].To[DELETION]);
    
    fputs("  ",stdout);
    
    PRINT_VALUE(InsertionLine[iprf].From[MATCH].To[EXTRA]);
    PRINT_VALUE(InsertionLine[iprf].From[INSERTION].To[EXTRA]);
    PRINT_VALUE(InsertionLine[iprf].From[DELETION].To[EXTRA]);
    
    fputs("  ",stdout);
    
    PRINT_VALUE(LastSeq[iprf].From[MATCH]);
    PRINT_VALUE(LastSeq[iprf].From[INSERTION]);
    PRINT_VALUE(LastSeq[iprf].From[DELETION]);
    
    fputs("  ",stdout);
    
    PRINT_VALUE(FirstSeq[iprf].To[MATCH]);
    PRINT_VALUE(FirstSeq[iprf].To[INSERTION]);
    PRINT_VALUE(FirstSeq[iprf].To[DELETION]);
    
    fputs("\n", stdout);
  }

  FreeProfile(&prf);
  return 0;
}
#endif
