/*
 * Gregorio is a program that translates gabc files to GregorioTeX
 * This file provides functions for determining glyphs from notes.
 *
 * Copyright (C) 2006-2025 The Gregorio Project (see CONTRIBUTORS.md)
 *
 * This file is part of Gregorio.
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "config.h"
#include <stdio.h>
#include <assert.h>
#include "bool.h"
#include "struct.h"
#include "messages.h"

#include "gabc.h"

static __inline gregorio_scanner_location *copy_note_location(
        const gregorio_note *const note, gregorio_scanner_location *const loc)
{
    loc->first_line = note->src_line;
    loc->first_column = note->src_column;
    loc->first_offset = note->src_offset;
    loc->last_line = note->src_line;
    loc->last_column = note->src_column;
    loc->last_offset = note->src_offset;
    return loc;
}

static __inline bool is_normal_punctum(const gregorio_note *const note)
{
    return note->u.note.shape == S_PUNCTUM
        && note->u.note.liquescentia != L_INITIO_DEBILIS;
}

static __inline bool is_punctum_inclinatum(const gregorio_shape shape,
        const bool determined_only)
{
    switch (shape) {
    case S_PUNCTUM_INCLINATUM_ASCENDENS:
    case S_PUNCTUM_INCLINATUM_STANS:
    case S_PUNCTUM_INCLINATUM_DESCENDENS:
        return true;
    case S_PUNCTUM_INCLINATUM_UNDETERMINED:
        return !determined_only;
    default:
        return false;
    }
}

/****************************
 *
 * This function is the basis of all the determination of glyphs. The
 * phylosophy of the function is to say : We have a glyph that we have
 * determined, and we have the following note, can we "add" it to the
 * glyph, or will it be the first note of another glyph ?
 *
 * current_glyph_type is the type of the current_glyph (current_glyph
 * as meant in gabc_det_glyphs_from_notes).  curent_pitch
 * is the height of the note that we want to "add" to the glyph.
 * last_pitch is the height of the last note of current_glyph.  shape
 * is the shape of the note we want to "add" to the glyph.
 *
 * The function returns a char, which meaning is explained below.
 *
 * end_of_glyph is a pointer to the result of the determination, here
 * are the meanings :
 *
 * DET_NO_END: we have successfully added the note to the glyph, and
 * we return the new type of the glyph. We may again add notes to the
 * glyph.
 *
 * DET_END_OF_PREVIOUS: we have not been able to add the note to the
 * glyph, so we will have to close the glyph.
 *
 * DET_END_OF_CURRENT: we have been able to add the note to the glyph,
 * but we won't be able to add more notes to the glyph, we can close
 * it. The new type is returned.
 *
 * DET_END_OF_BOTH: we haven't been able to add the note to the glyph,
 * and we won't be able to add notes to the new glyph. This special
 * case is quite rare, we use it for trivirga, tristropha, etc.
 *
 * When we encouter a S_QUADRATUM (or S_QUILISMA_QUADRATUM), we build
 * a new glyph with the (temporary) shape G_PES_QUADRATUM_FIRST_PART
 * (or G_PES_QUILISMA_QUADRATUM_FIRST_PART), and we wait for the next
 * note.
 *
 ****************************/

static char add_note_to_a_glyph(gregorio_glyph_type current_glyph_type,
        char current_pitch, char last_pitch, gregorio_shape shape,
        gregorio_liquescentia liquescentia,
        gregorio_note *current_glyph_first_note,
        gabc_determination *end_of_glyph,
        gregorio_shape *punctum_inclinatum_orientation)
{
    #define this_note_starts_new_glyph { \
        next_glyph_type = G_PUNCTUM; \
        *end_of_glyph = DET_END_OF_PREVIOUS; \
    }

    /* next glyph type is the type of the glyph that will be returned (the
     * new type of the glyph with the note added to it, or the type of the
     * new glyph with the note alone. */
    gregorio_glyph_type next_glyph_type = G_UNDETERMINED;

    *end_of_glyph = DET_NO_END;

    /* here we separate notes that would be logically in the same glyph
     * but that are too far to be so */
    if (last_pitch) {
        if (current_pitch - last_pitch > MAX_AMBITUS
                || current_pitch - last_pitch < -MAX_AMBITUS) {
            current_glyph_type = G_UNDETERMINED;
        }
    }

    switch (shape) {
    case S_LINEA_PUNCTUM:
    case S_LINEA:
        next_glyph_type = G_PUNCTUM;
        *end_of_glyph = DET_END_OF_BOTH;
        break;
    case S_FLAT:
    case S_FLAT_PAREN:
    case S_FLAT_SOFT:
    case S_SHARP:
    case S_SHARP_PAREN:
    case S_SHARP_SOFT:
    case S_NATURAL:
    case S_NATURAL_PAREN:
    case S_NATURAL_SOFT:
        next_glyph_type = G_ALTERATION;
        *end_of_glyph = DET_END_OF_BOTH;
        break;
    case S_PUNCTUM:
        /*
         * we determine here the shape of the thing if it is made of puncta
         */
        if (current_pitch == last_pitch) {
            this_note_starts_new_glyph;
            break;
        }
        switch (current_glyph_type) {
        case G_PUNCTUM:
            if (current_pitch > last_pitch) {
                next_glyph_type = G_PODATUS;
            } else {
                next_glyph_type = G_FLEXA;
            }
            break;
        case G_PODATUS:
            if (current_pitch > last_pitch) {
                if (is_normal_punctum(current_glyph_first_note)) {
                    next_glyph_type = G_SCANDICUS;
                    *end_of_glyph = DET_END_OF_CURRENT;
                } else {
                    this_note_starts_new_glyph;
                }
            } else {
                next_glyph_type = G_TORCULUS;
            }
            break;
        case G_PES_QUADRATUM_FIRST_PART:
            if (current_pitch > last_pitch) {
                next_glyph_type = G_PES_QUADRATUM;
                *end_of_glyph = DET_END_OF_CURRENT;
            } else {
                next_glyph_type = G_FLEXA;
            }
            break;
        case G_PES_QUILISMA_QUADRATUM_FIRST_PART:
            if (current_pitch > last_pitch) {
                next_glyph_type = G_PES_QUADRATUM;
                *end_of_glyph = DET_END_OF_CURRENT;
            } else {
                this_note_starts_new_glyph;
            }
            break;
        case G_PES_ASCENDENS_ORISCUS:
        case G_PES_DESCENDENS_ORISCUS:
            if (current_pitch > last_pitch) {
                next_glyph_type = G_SALICUS;
            } else {
                this_note_starts_new_glyph;
            }
            break;
        case G_SALICUS:
            if (current_pitch < last_pitch) {
                next_glyph_type = G_SALICUS_FLEXUS;
                *end_of_glyph = DET_END_OF_CURRENT;
            } else {
                this_note_starts_new_glyph;
            }
            break;
        case G_FLEXA:
            if (current_pitch > last_pitch) {
                if (is_normal_punctum(current_glyph_first_note)) {
                    next_glyph_type = G_PORRECTUS;
                } else {
                    this_note_starts_new_glyph;
                }
            } else {
                if (liquescentia & L_DEMINUTUS) {
                    *end_of_glyph = DET_END_OF_CURRENT;
                    next_glyph_type = G_ANCUS;
                } else {
                    this_note_starts_new_glyph;
                }
            }
            break;
        case G_TORCULUS:
            if (current_pitch > last_pitch) {
                next_glyph_type = G_TORCULUS_RESUPINUS;
            } else if (liquescentia == L_DEMINUTUS) {
                *end_of_glyph = DET_END_OF_CURRENT;
                next_glyph_type = G_TORCULUS_LIQUESCENS;
            } else {
                this_note_starts_new_glyph;
            }
            break;
        case G_TORCULUS_RESUPINUS:
            if (current_pitch > last_pitch) {
                this_note_starts_new_glyph;
            } else {
                *end_of_glyph = DET_END_OF_CURRENT;
                next_glyph_type = G_TORCULUS_RESUPINUS_FLEXUS;
            }
            break;
        case G_PORRECTUS:
            if (current_pitch > last_pitch) {
                this_note_starts_new_glyph;
            } else {
                *end_of_glyph = DET_END_OF_CURRENT;
                next_glyph_type = G_PORRECTUS_FLEXUS;
            }
            break;
        default:
            this_note_starts_new_glyph;
            break;
        }
        break;
    case S_ORISCUS_UNDETERMINED:
    case S_ORISCUS_ASCENDENS:
    case S_ORISCUS_DESCENDENS:
    case S_ORISCUS_DEMINUTUS:
    case S_QUILISMA:
        this_note_starts_new_glyph;
        break;
    case S_VIRGA:
        if (current_glyph_type == G_VIRGA && last_pitch == current_pitch) {
            next_glyph_type = G_BIVIRGA;
        } else {
            if (current_glyph_type == G_BIVIRGA && last_pitch == current_pitch) {
                next_glyph_type = G_TRIVIRGA;
            } else {
                *end_of_glyph = DET_END_OF_PREVIOUS;
                next_glyph_type = G_VIRGA;
            }
        }
        break;
    case S_VIRGA_REVERSA:
        *end_of_glyph = DET_END_OF_PREVIOUS;
        next_glyph_type = G_VIRGA_REVERSA;
        break;
    case S_BIVIRGA:
        if (current_glyph_type == G_VIRGA && last_pitch == current_pitch) {
            *end_of_glyph = DET_END_OF_CURRENT;
            next_glyph_type = G_TRIVIRGA;
        } else {
            *end_of_glyph = DET_END_OF_PREVIOUS;
            next_glyph_type = G_BIVIRGA;
        }
        break;
    case S_TRIVIRGA:
        *end_of_glyph = DET_END_OF_BOTH;
        next_glyph_type = G_TRIVIRGA;
        break;
    case S_QUADRATUM:
        *end_of_glyph = DET_END_OF_PREVIOUS;
        next_glyph_type = G_PES_QUADRATUM_FIRST_PART;
        break;
    case S_QUILISMA_QUADRATUM:
        *end_of_glyph = DET_END_OF_PREVIOUS;
        next_glyph_type = G_PES_QUILISMA_QUADRATUM_FIRST_PART;
        break;
    case S_ORISCUS_SCAPUS_UNDETERMINED:
    case S_ORISCUS_SCAPUS_ASCENDENS:
        if (current_glyph_type == G_PUNCTUM && last_pitch < current_pitch) {
            next_glyph_type = G_PES_ASCENDENS_ORISCUS;
            /* Therefore G_PES_ASCENDENS_ORISCUS might be fronting an
             * undetermined oriscus scapus.  This will be resolved during
             * oriscus orientation determination */
        } else {
            this_note_starts_new_glyph;
        }
        break;
    case S_ORISCUS_SCAPUS_DESCENDENS:
        if (current_glyph_type == G_PUNCTUM && last_pitch < current_pitch) {
            next_glyph_type = G_PES_DESCENDENS_ORISCUS;
        } else {
            this_note_starts_new_glyph;
        }
        break;
    case S_PUNCTUM_INCLINATUM_UNDETERMINED:
    case S_PUNCTUM_INCLINATUM_ASCENDENS:
    case S_PUNCTUM_INCLINATUM_STANS:
    case S_PUNCTUM_INCLINATUM_DESCENDENS:
        /*
         * Warning : this part of the code is specific to the
         * declarations of the header file
         */
        if (current_glyph_type > G_PUNCTA_INCLINATA) {
            /*
             * meaning that the previous glyph is not a combination of puncta
             * inclinata, see header file for details
             */
            *end_of_glyph = DET_END_OF_PREVIOUS;
            next_glyph_type = G_PUNCTUM_INCLINATUM;
            break;
        }
        if (current_pitch == last_pitch) {
            *end_of_glyph = DET_END_OF_PREVIOUS;
            next_glyph_type = G_PUNCTUM_INCLINATUM;
            break;
        }
        if (is_punctum_inclinatum(shape, true)
                && *punctum_inclinatum_orientation != shape
                && *punctum_inclinatum_orientation
                != S_PUNCTUM_INCLINATUM_UNDETERMINED) {
            /* change of orientation ends the glyph */
            *end_of_glyph = DET_END_OF_PREVIOUS;
            next_glyph_type = G_PUNCTUM_INCLINATUM;
            break;
        }
        switch (current_glyph_type) {
        case G_PUNCTUM_INCLINATUM:
            if (last_pitch < current_pitch) {
                next_glyph_type = G_2_PUNCTA_INCLINATA_ASCENDENS;
            } else {
                next_glyph_type = G_2_PUNCTA_INCLINATA_DESCENDENS;
            }
            break;
        case G_2_PUNCTA_INCLINATA_ASCENDENS:
            if (last_pitch < current_pitch) {
                next_glyph_type = G_3_PUNCTA_INCLINATA_ASCENDENS;
            } else {
                next_glyph_type = G_PUNCTA_INCLINATA;
            }
            break;
        case G_3_PUNCTA_INCLINATA_ASCENDENS:
            if (last_pitch < current_pitch) {
                next_glyph_type = G_4_PUNCTA_INCLINATA_ASCENDENS;
            } else {
                next_glyph_type = G_PUNCTA_INCLINATA;
            }
            break;
        case G_4_PUNCTA_INCLINATA_ASCENDENS:
            if (last_pitch < current_pitch) {
                next_glyph_type = G_5_PUNCTA_INCLINATA_ASCENDENS;
            } else {
                next_glyph_type = G_PUNCTA_INCLINATA;
            }
            break;
        case G_2_PUNCTA_INCLINATA_DESCENDENS:
            if (last_pitch < current_pitch) {
                next_glyph_type = G_PUNCTA_INCLINATA;
            } else {
                next_glyph_type = G_3_PUNCTA_INCLINATA_DESCENDENS;
            }
            break;
        case G_3_PUNCTA_INCLINATA_DESCENDENS:
            if (last_pitch < current_pitch) {
                next_glyph_type = G_PUNCTA_INCLINATA;
            } else {
                next_glyph_type = G_4_PUNCTA_INCLINATA_DESCENDENS;
            }
            break;
        case G_4_PUNCTA_INCLINATA_DESCENDENS:
            if (last_pitch < current_pitch) {
                next_glyph_type = G_PUNCTA_INCLINATA;
            } else {
                next_glyph_type = G_5_PUNCTA_INCLINATA_DESCENDENS;
            }
            break;
        default:
            next_glyph_type = G_PUNCTA_INCLINATA;
            break;
        }
        break;
    case S_STROPHA:
        if (last_pitch != current_pitch) {
            *end_of_glyph = DET_END_OF_PREVIOUS;
            next_glyph_type = G_STROPHA;
            break;
        }
        switch (current_glyph_type) {
        case G_STROPHA:
            next_glyph_type = G_DISTROPHA;
            break;
        case G_DISTROPHA:
            *end_of_glyph = DET_END_OF_CURRENT;
            next_glyph_type = G_TRISTROPHA;
            break;
        default:
            *end_of_glyph = DET_END_OF_PREVIOUS;
            next_glyph_type = G_STROPHA;
            break;
        }
        break;
    case S_DISTROPHA:
        if (last_pitch == current_pitch && current_glyph_type == G_STROPHA) {
            *end_of_glyph = DET_END_OF_CURRENT;
            next_glyph_type = G_TRISTROPHA;
        } else {
            *end_of_glyph = DET_END_OF_PREVIOUS;
            next_glyph_type = G_DISTROPHA;
        }
        break;
    case S_TRISTROPHA:
        *end_of_glyph = DET_END_OF_BOTH;
        next_glyph_type = G_TRISTROPHA;
        break;
    default:
        /* not reachable unless there's a programming error */
        /* LCOV_EXCL_START */
        gregorio_fail2(add_note_to_a_glyph, "unexpected shape: %s",
                gregorio_shape_to_string(shape));
        break;
        /* LCOV_EXCL_STOP */
    }
    /* end of the main switch */

    if (current_glyph_type == G_UNDETERMINED) {
        /*
         * means that this is the first glyph or that the previous glyph has
         * already been added
         */
        if (*end_of_glyph == DET_END_OF_PREVIOUS) {
            *end_of_glyph = DET_NO_END;
        } else {
            if (*end_of_glyph == DET_END_OF_BOTH) {
                *end_of_glyph = DET_END_OF_CURRENT;
            }
        }
    }

    /*
     * WARNING : Ugly section of the code, just some kind of patch for it to work
     * with fonts that can't handle large intervals.
     */
    /* here we separate notes that would be logically in the same glyph
     * but that are too far to be so, we already said to the function that
     * it was not the same glyph, there we must say that the previous
     * glyph has ended. */
    if (last_pitch) {
        if (current_pitch - last_pitch > MAX_AMBITUS
                || current_pitch - last_pitch < -MAX_AMBITUS) {
            if (*end_of_glyph == DET_END_OF_CURRENT
                    || *end_of_glyph == DET_END_OF_BOTH) {
                /* There is no current way for the code to end up here, but
                 * we'll leave it in because it's a good safety precaution in
                 * case something new is added in the future */
                /* LCOV_EXCL_START */
                gregorio_message(_("Encountered the need to switch "
                            "DET_END_OF_CURRENT to DET_END_OF_BOTH because of "
                            "overly large ambitus"),
                            "add_note_to_a_glyph", VERBOSITY_WARNING,
                            __LINE__);
                *end_of_glyph = DET_END_OF_BOTH;
            } else {
                /* LCOV_EXCL_STOP */
                *end_of_glyph = DET_END_OF_PREVIOUS;
            }
        }
    }

    switch (shape) {
    case S_PUNCTUM_INCLINATUM_ASCENDENS:
    case S_PUNCTUM_INCLINATUM_STANS:
    case S_PUNCTUM_INCLINATUM_DESCENDENS:
        *punctum_inclinatum_orientation = shape;
        break;
    case S_PUNCTUM_INCLINATUM_UNDETERMINED:
        break;
    default:
        *punctum_inclinatum_orientation = S_PUNCTUM_INCLINATUM_UNDETERMINED;
        break;
    }

    return next_glyph_type;
}

static bool is_cavum(gregorio_note *note, gregorio_note *last_note)
{
    bool is_cavum = false;
    for (; note; note = note->next) {
        if (note->type == GRE_NOTE) {
            is_cavum = is_cavum || note->u.note.is_cavum;
        }
        /* don't look past the last note */
        if (note == last_note) {
            break;
        }
    }
    return is_cavum;
}

/****************************
 *
 * First see the comments of
 * gabc_det_glyphs_from_notes. This function is used when
 * we have finished to determine a glyph. We have the last glyph that
 * have been added: last_glyph. The glyph we want to add is given by
 * glyph_type and liquescentia.
 *
 * The glyph we want to add goes from first_note to current_note, we
 * isolate these notes from the notes that won't be in the glyph, and
 * we add the glyph to the list_of_glyphs.
 *
****************************/

static gregorio_note *close_normal_glyph(gregorio_glyph **last_glyph,
        gregorio_glyph_type glyph_type, gregorio_note **first_note,
        gregorio_liquescentia liquescentia, gregorio_note *current_note)
{
    gregorio_note *new_current_note = current_note;
    gregorio_scanner_location loc;

    /* patch to have good glyph type in the case where a glyph ends by a note
     * with shape S_QUADRATUM */
    if (glyph_type == G_PES_QUADRATUM_FIRST_PART
            || glyph_type == G_PES_QUILISMA_QUADRATUM_FIRST_PART) {
        glyph_type = G_PUNCTUM;
    }

    gregorio_add_glyph(last_glyph, glyph_type, *first_note, liquescentia,
            is_cavum(*first_note, current_note));

    if (current_note->next) {
        current_note->next->previous = NULL;
        *first_note = current_note->next;
        current_note->next = NULL;
    }
    /* here we "patch" the structure for bivirga, tristropha, etc. */
    /* the idea is not to have a S_BIVIRGA in the shape of the note (which is
     * dirty) but rather a G_BIVIRGA in the glyph (which is the case now) and
     * two virgas */

    if (glyph_type == G_BIVIRGA || glyph_type == G_DISTROPHA
            || glyph_type == G_TRIVIRGA || glyph_type == G_TRISTROPHA
            || glyph_type == G_DISTROPHA_AUCTA
            || glyph_type == G_TRISTROPHA_AUCTA) {
        gregorio_go_to_first_note(&current_note);
        while (current_note) {
            /* a variable necessary for the patch for G_BIVIRGA & co. */
            gregorio_note *added_notes = NULL;
            gregorio_note *next_note = NULL;
            if (current_note->type == GRE_NOTE) {
                switch (current_note->u.note.shape) {
                case S_TRIVIRGA:
                    gregorio_add_note(&added_notes, current_note->u.note.pitch,
                            S_VIRGA, _NO_SIGN, L_NO_LIQUESCENTIA, current_note,
                            copy_note_location(current_note, &loc));
                    /* fall through */
                case S_BIVIRGA:
                    gregorio_add_note(&added_notes, current_note->u.note.pitch,
                            S_VIRGA, _NO_SIGN, L_NO_LIQUESCENTIA, current_note,
                            copy_note_location(current_note, &loc));
                    gregorio_add_note(&added_notes, current_note->u.note.pitch,
                            S_VIRGA, current_note->signs,
                            current_note->u.note.liquescentia, current_note,
                            copy_note_location(current_note, &loc));
                    break;
                case S_TRISTROPHA:
                    gregorio_add_note(&added_notes, current_note->u.note.pitch,
                            S_STROPHA, _NO_SIGN, L_NO_LIQUESCENTIA,
                            current_note,
                            copy_note_location(current_note, &loc));
                    /* fall through */
                case S_DISTROPHA:
                    gregorio_add_note(&added_notes, current_note->u.note.pitch,
                            S_STROPHA, _NO_SIGN, L_NO_LIQUESCENTIA,
                            current_note,
                            copy_note_location(current_note, &loc));
                    gregorio_add_note(&added_notes, current_note->u.note.pitch,
                            S_STROPHA, current_note->signs,
                            current_note->u.note.liquescentia, current_note,
                            copy_note_location(current_note, &loc));
                    break;
                default:
                    break;
                }
            }
            if (!added_notes) {
                /* nothing added, but we must go on in case it's
                 * virga+bivirga = trivirga or stropa+bistropha = tristropha */
                if (current_note->next) {
                    current_note = current_note->next;
                    continue;
                } else {
                    break;
                }
            }
            next_note = current_note->next;
            /* now we have what we want, we set up the links and free the old
             * note */
            if (next_note) {
                current_note->next->previous = added_notes;
                added_notes->next = next_note;
            }
            if (current_note == new_current_note) {
                new_current_note = added_notes;
            }
            gregorio_go_to_first_note(&added_notes);
            if (current_note->previous) {
                current_note->previous->next = added_notes;
                added_notes->previous = current_note->previous;
            }
            /* Detaching current_note is not strictly necessary here because we
             * are effectively plucking out added_notes into its own glyph;
             * however, detaching the note is safer if this behavior changes in
             * the future because gregorio_free_one_note nullifies surrounding
             * pointers */
            current_note->next = NULL;
            current_note->previous = NULL;
            gregorio_free_one_note(&current_note);
            if (!next_note) {
                current_note = added_notes;
                break;
            }
            current_note = next_note;
        }
        gregorio_go_to_first_note(&current_note);
        /* finally we set the just added glyph first_note to current_note */
        (*last_glyph)->u.notes.first_note = current_note;
    }
    return new_current_note;
}

static gregorio_note *close_fused_glyph(gregorio_glyph **last_glyph,
        gregorio_glyph_type glyph_type, gregorio_note **first_note,
        gregorio_liquescentia liquescentia, gregorio_note *current_note)
{
    (*first_note)->u.note.liquescentia |= (liquescentia & L_FUSED);
    return close_normal_glyph(last_glyph, glyph_type, first_note, liquescentia,
            current_note);
}

static gregorio_note *next_non_texverb_note(gregorio_note *first_note,
        gregorio_note *last_note)
{
    gregorio_not_null(first_note, gregorio_note, return NULL);

    if (first_note == last_note) {
        /* not reachable unless there's a programming error */
        /* LCOV_EXCL_START */
        gregorio_assert_only(first_note->type != GRE_TEXVERB_GLYPH,
                next_non_texverb_note, "Unexpected texverb at start of iteration");
        return first_note;
        /* LCOV_EXCL_STOP */
    }

    for (first_note = first_note->next; first_note && first_note != last_note;
            first_note = first_note->next) {
        if (first_note->type != GRE_TEXVERB_GLYPH) {
            return first_note;
        }
    }

    gregorio_assert_only(!first_note || first_note->type != GRE_TEXVERB_GLYPH,
            next_non_texverb_note, "Unexpected texverb at end of iteration");

    return last_note;
}

static void add_intervening_texverbs(gregorio_glyph **last_glyph,
        gregorio_note *first_note, gregorio_note *last_note)
{
    for ( ; first_note && first_note->type == GRE_TEXVERB_GLYPH; ) {
        bool on_last_note = (first_note == last_note);

        gregorio_add_unpitched_element_as_glyph(last_glyph, first_note->type,
                &(first_note->u.other), _NO_SIGN, first_note->texverb);
        first_note->texverb = 0;
        gregorio_free_one_note(&first_note);

        if (on_last_note) {
            break;
        }
    }
}

static gregorio_note *close_fusion_glyph(gregorio_glyph **last_glyph,
        gregorio_note **first_note, gregorio_liquescentia liquescentia,
        gregorio_note *real_last_note,
        gregorio_shape *const punctum_inclinatum_orientation)
{
    bool first = true;
    gregorio_note *last_note, *texverb_tail;
    gregorio_note *next;
    int prev_shift = 0, shift, shift2;
    gregorio_note *result;

    gregorio_assert((*first_note)->type != GRE_TEXVERB_GLYPH,
            close_fusion_glyph, "Unexpected texverb at start of fusion",
            return real_last_note);

    for (last_note = real_last_note;
            last_note != *first_note && last_note->type == GRE_TEXVERB_GLYPH;
            last_note = last_note->previous) {
        /* skip over the trailing texverbs */
    }
    texverb_tail = (last_note == real_last_note)? NULL : last_note->next;

    for (;;) {
        bool processed = false;

        /* fusion must work through glyph-level texverbs */
        if ((*first_note)->type == GRE_TEXVERB_GLYPH) {
            gregorio_add_unpitched_element_as_glyph(last_glyph,
                    (*first_note)->type, &((*first_note)->u.other), _NO_SIGN,
                    (*first_note)->texverb);
            (*first_note)->texverb = 0;
            gregorio_assert(*first_note != last_note,
                    close_fusion_glyph, "Unexpected texverb at end of fusion",
                    return last_note);
            gregorio_free_one_note(first_note);
        }

        if (*first_note == last_note) {
            close_fused_glyph(last_glyph, G_PUNCTUM, first_note,
                        liquescentia & ~TAIL_LIQUESCENTIA_MASK, last_note);
            return last_note;
        }

        gregorio_assert((*first_note)->next, close_fusion_glyph,
                "Unexpected single note during fusion", return last_note);

        next = next_non_texverb_note(*first_note, last_note);

        shift = next->u.note.pitch - (*first_note)->u.note.pitch;
        if (shift != 0 && next == last_note) {
            /* there are exactly two notes left, so we end fusion */
            add_intervening_texverbs(last_glyph, (*first_note)->next, next);
            result = close_fused_glyph(last_glyph,
                    shift < 0? G_FLEXA : G_PODATUS, first_note,
                    liquescentia, last_note);
            if (texverb_tail) {
                add_intervening_texverbs(last_glyph, texverb_tail,
                        real_last_note);
            }
            return result;
        }
        if (prev_shift >= 0 && shift < 0) {
            /* check for a porrectus-like flexus */
            gregorio_note *next_next = next_non_texverb_note(next, last_note);
            gregorio_assert(next_next, close_fusion_glyph,
                    "Unexpected end of notes during fusion", return last_note);
            shift2 = next_next->u.note.pitch - next->u.note.pitch;
            if (shift2 > 0) {
                if (next_next == last_note) {
                    /* there are exactly three notes left in a porrectus shape,
                     * so we end fusion */
                    add_intervening_texverbs(last_glyph, (*first_note)->next,
                            next);
                    add_intervening_texverbs(last_glyph, next->next, next_next);
                    result = close_fused_glyph(last_glyph,
                            G_PORRECTUS, first_note,
                            liquescentia, last_note);
                    add_intervening_texverbs(last_glyph, texverb_tail,
                            real_last_note);
                    return result;
                }
                /* found a porrectus-like flexus */
                add_intervening_texverbs(last_glyph, *first_note, next);
                close_fused_glyph(last_glyph,
                        shift < 0? G_FLEXA : G_PODATUS, first_note,
                        liquescentia & ~TAIL_LIQUESCENTIA_MASK, next);
                prev_shift = shift2;
                processed = true;
            }
        }

        if (!processed) {
            /* didn't find anything interesting, so fuse the single note */
            gabc_determination ignored;
            gregorio_glyph_type next_glyph_type =
                    add_note_to_a_glyph(G_UNDETERMINED,
                    (*first_note)->u.note.pitch, 0, (*first_note)->u.note.shape,
                    (*first_note)->u.note.liquescentia, *first_note, &ignored,
                    punctum_inclinatum_orientation);
            close_fused_glyph(last_glyph, next_glyph_type, first_note,
                    liquescentia & ~TAIL_LIQUESCENTIA_MASK, *first_note);
            prev_shift = shift;
        }

        if (first) {
            first = false;
            liquescentia = (liquescentia & TAIL_LIQUESCENTIA_MASK) | L_FUSED;
        }
    }
}

static gregorio_note *close_glyph(gregorio_glyph **last_glyph,
        gregorio_glyph_type glyph_type, gregorio_note **first_note,
        gregorio_liquescentia liquescentia, gregorio_note *current_note,
        gregorio_shape *const punctum_inclinatum_orientation)
{
    /* special case for fusion when rarer shapes don't exist */
    if (glyph_type == G_TORCULUS) {
        switch ((*first_note)->u.note.shape) {
        case S_ORISCUS_UNDETERMINED:
        case S_ORISCUS_ASCENDENS:
        case S_ORISCUS_DESCENDENS:
        case S_ORISCUS_SCAPUS_UNDETERMINED:
        case S_ORISCUS_SCAPUS_ASCENDENS:
        case S_ORISCUS_SCAPUS_DESCENDENS:
            glyph_type = G_FUSED;
            break;
        default:
            break;
        }
    }

    if (glyph_type == G_FUSED) {
        return close_fusion_glyph(last_glyph, first_note, liquescentia,
                current_note, punctum_inclinatum_orientation);
    } else {
        return close_normal_glyph(last_glyph, glyph_type, first_note,
                liquescentia, current_note);
    }
}

/* a small function to automatically determine the pitch of a custo : it is
 * the pitch of the next note, but we must take care of the clef changes, as
 * custo are (normally and for now) only present before clef changes. */
/* TODO: there may be a side effect with the flated keys... */

static char gabc_determine_custo_pitch(gregorio_note *current_note,
        int current_key, const gregorio_score *const score)
{
    int pitch_difference = 0;
    int newkey;
    for (; current_note; current_note = current_note->next) {
        switch (current_note->type) {
        case GRE_CLEF:
            newkey = gregorio_calculate_new_key(current_note->u.clef);
            pitch_difference = newkey - current_key;
            break;

        case GRE_NOTE:
            pitch_difference =
                    (int) current_note->u.note.pitch - pitch_difference;
            while (pitch_difference < LOWEST_PITCH) {
                pitch_difference += 7;
            }
            while (pitch_difference > score->highest_pitch) {
                pitch_difference -= 7;
            }
            assert(pitch_difference >= LOWEST_PITCH
                    && pitch_difference <= score->highest_pitch);
            return (char) pitch_difference;

        default:
            break;
        }
    }
    return DUMMY_PITCH;
}

/****************************
 *
 * Function called with a list of gregorio_notes as argument, this
 * list is determined from gabc notation by the function
 * gabc_det_notes_from_string.
 *
 * In this function we create a list of glyphs, by determining their
 * type according to the sequence of notes (we look at their height,
 * shape, etc.). Each glyph has a pointer gregorio_note *first_note
 * that will be filled with the good note (and notes will be cut as
 * explained in the comments on close_glyph function).
 *
 * Here is how it works :
 * current_glyph is the glyph we are currently determining, that is to
 * say a glyph whose type may change according to the note we will add
 * to it.
 * current_note is the note we are currently dealing with.
 * Let's take an example, if the list of notes is ab, the first step
 * (current_glyph: undetermined, current_note: a) will be to add a to
 * nothing, that will give a glyph "punctum" containing only one note:
 * a. In the second step (current_glyph: punctum, current_note: b) we
 * transform the glyph in "pes".
 * For special notes like a key change or something, we stop the
 * determination of the current glyph, we delete the note and we add
 * it as a glyph.
 *
 * When we determine the glyphs we can encounter the shapes
 * S_QUADRATUM and S_QUILISMA_QUADRATUM, which means that the
 * corresponding note is the first note of a pes quadratum (it can
 * have the shape quilisma for very rare forms). But these shapes must
 * not appear in the final form of the score, and we transform them
 * respectively in punctum and quilisma (and the glyph type must be
 * pes_quadratum, but it is done in add_note_to_a_glyph).
 *
****************************/

/* this function updates current_key with the new values (with clef changes) */

gregorio_glyph *gabc_det_glyphs_from_notes(gregorio_note *current_note,
        int *current_key, gregorio_shape *const punctum_inclinatum_orientation,
        const gregorio_score *const score)
{
    /* the first note of the current glyph, to be able to close it well:
     * later we will cut the link (next_notes and previous_note) between
     * this note and the previous one */
    gregorio_note *current_glyph_first_note = current_note;

    /* the last glyph we have totally determined. It is automatically
     * updated by close_glyph(). */
    gregorio_glyph *last_glyph = NULL;

    /* type of the glyph we are currently determining */
    gregorio_glyph_type current_glyph_type = G_UNDETERMINED;
    gregorio_glyph_type next_glyph_type = G_UNDETERMINED;
    char last_pitch = USELESS_VALUE;
    gregorio_note *next_note = NULL;

    /* determination of end of glyphs, see comments on
     * add_note_to_a_glyph */
    gabc_determination end_of_glyph = DET_NO_END;

    /* a char representing the liquescentia of the current glyph */
    gregorio_liquescentia liquescentia = L_NO_LIQUESCENTIA;
    gregorio_liquescentia head_liquescentia;
    bool autofuse = false, first_autofused_note = false;

    gregorio_assert(current_note, gabc_det_glyphs_from_notes,
            "current_note may not be NULL", return NULL);

    gregorio_go_to_first_note(&current_note);

    while (current_note) {
        bool add = true;

        next_note = current_note->next;
        if (current_note->type != GRE_NOTE) {
            gregorio_type type = current_note->type;
            char pitch = USELESS_VALUE;
            bool force = false;
            gregorio_sign sign = _NO_SIGN;

            if (current_glyph_type == G_FUSED
                    && (type == GRE_TEXVERB_GLYPH || type == GRE_TEXVERB_ELEMENT)) {
                current_note = next_note;
                continue;
            }

            if (current_glyph_type != G_UNDETERMINED) {
                close_glyph(&last_glyph, next_glyph_type,
                        &current_glyph_first_note, liquescentia,
                        current_note->previous, punctum_inclinatum_orientation);
                current_glyph_type = G_UNDETERMINED;
                liquescentia = L_NO_LIQUESCENTIA;
            }

            switch (type) {
            case GRE_CLEF:
                pitch = current_note->u.note.pitch;
                *current_key = gregorio_calculate_new_key(current_note->u.clef);
                break;

            case GRE_CUSTOS:
                pitch = gabc_determine_custo_pitch(current_note->next,
                        *current_key, score);
                break;

            case GRE_MANUAL_CUSTOS:
                pitch = current_note->u.note.pitch;
                type = GRE_CUSTOS;
                force = true;
                break;

            case GRE_BAR:
                /* we calculate the signs of the bars */
                if (current_note->signs == _V_EPISEMA) {
                    sign = _V_EPISEMA;
                } else {
                    sign = current_note->special_sign;
                }
                if (current_note->h_episema_above) {
                    if (sign == _V_EPISEMA) {
                        sign = _V_EPISEMA_BAR_H_EPISEMA;
                    } else {
                        sign = _BAR_H_EPISEMA;
                    }
                }
                break;

            case GRE_AUTOFUSE_START:
                autofuse = true;
                first_autofused_note = true;
                add = false;
                break;

            case GRE_AUTOFUSE_END:
                autofuse = false;
                add = false;
                break;

            default:
                /* do nothing */
                break;
            }

            if (add) {
                if (pitch == USELESS_VALUE) {
                    gregorio_add_unpitched_element_as_glyph(&last_glyph, type,
                            &(current_note->u.other), sign,
                            current_note->texverb);
                } else if (type == GRE_CLEF) {
                    gregorio_add_clef_as_glyph(&last_glyph,
                            current_note->u.clef, current_note->texverb);
                } else {
                    gregorio_add_pitched_element_as_glyph(&last_glyph, type,
                            pitch, force, current_note->texverb);
                }
            }
            current_glyph_first_note = current_note->next;
            current_note->texverb = 0;
            gregorio_free_one_note(&current_note);
            last_pitch = USELESS_VALUE;
            continue;
        }

        /* first we do what must be done with liquescentia */
        head_liquescentia = current_note->u.note.liquescentia
                & (L_INITIO_DEBILIS | L_FUSED);
        if (head_liquescentia) {
            /* initio debilis or fused */
            if (current_glyph_type != G_UNDETERMINED) {
                /* if it is not the first glyph */
                close_glyph(&last_glyph, current_glyph_type,
                        &current_glyph_first_note,
                        liquescentia, current_note->previous,
                        punctum_inclinatum_orientation);
                current_glyph_type = G_UNDETERMINED;
            }
            liquescentia = head_liquescentia;
        }

        if (autofuse) {
            if (current_glyph_first_note == current_note) {
                if (first_autofused_note) {
                    first_autofused_note = false;
                } else {
                    if (!(current_note->u.note.liquescentia & L_INITIO_DEBILIS)) {
                        current_note->u.note.liquescentia |= L_FUSED;
                        liquescentia |= L_FUSED;
                    }
                }
            } else {
                /* only handle the multi-note case here; the single-note
                 * case will be added normally by the state machine */
                if (is_tail_liquescentia(current_note->u.note.liquescentia)
                        && current_glyph_type > G_PUNCTA_INCLINATA) {
                    /* once we hit a liquescent, that's the end of a string of
                     * fused notes */
                    liquescentia |= current_note->u.note.liquescentia;
                    current_note = close_glyph(&last_glyph, G_FUSED,
                            &current_glyph_first_note, liquescentia,
                            current_note, punctum_inclinatum_orientation);
                    current_glyph_type = G_UNDETERMINED;
                    liquescentia = L_NO_LIQUESCENTIA;
                    add = false;
                } else {
                    switch (current_note->u.note.shape) {
                    case S_PUNCTUM:
                    case S_ORISCUS_UNDETERMINED:
                    case S_ORISCUS_ASCENDENS:
                    case S_ORISCUS_DESCENDENS:
                    case S_ORISCUS_SCAPUS_UNDETERMINED:
                    case S_ORISCUS_SCAPUS_ASCENDENS:
                    case S_ORISCUS_SCAPUS_DESCENDENS:
                    case S_QUILISMA:
                    case S_QUADRATUM:
                    case S_QUILISMA_QUADRATUM:
                    case S_VIRGA:
                        /* these are fusible */
                        if (current_glyph_type <= G_PUNCTA_INCLINATA
                                || current_note->u.note.shape != S_PUNCTUM) {
                            /* if we had some puncta inclinata, then end them */
                            /* if the current shape is not a punctum, start a
                             * new shape */
                            close_glyph(&last_glyph, current_glyph_type,
                                    &current_glyph_first_note,
                                    liquescentia, current_note->previous,
                                    punctum_inclinatum_orientation);
                            if (!(current_note->u.note.liquescentia
                                        & L_INITIO_DEBILIS)) {
                                current_note->u.note.liquescentia |= L_FUSED;
                                liquescentia |= L_FUSED;
                            }
                        }
                        next_glyph_type = current_glyph_type = G_FUSED;
                        add = false;
                        break;

                    default:
                        /* not fusible; will be added normally by the state
                         * machine */
                        break;
                    }
                }
            }
        }

        if (add) {
            next_glyph_type = add_note_to_a_glyph(current_glyph_type,
                    current_note->u.note.pitch, last_pitch,
                    current_note->u.note.shape,
                    current_note->u.note.liquescentia,
                    current_glyph_first_note, &end_of_glyph,
                    punctum_inclinatum_orientation);
        }

        /* patch to have good shapes in the special cases of pes quadratum and
         * pes quilisma quadratum. */
        switch (current_note->u.note.shape) {
        case S_QUADRATUM:
            current_note->u.note.shape = S_PUNCTUM;
            break;

        case S_QUILISMA_QUADRATUM:
            current_note->u.note.shape = S_QUILISMA;
            break;

        default:
            /* do nothing */
            break;
        }

        if (add) {
            /* see comments on add_note_to_a_glyph for the meaning of
             * end_of_glyph */
            switch (end_of_glyph) {
            case DET_NO_END:
                current_glyph_type = next_glyph_type;
                /*
                 * we deal with liquescentia
                 */
                if (is_tail_liquescentia(current_note->u.note.liquescentia)) {
                    /* special cases of oriscus descendens, treated like normal
                     * oriscus in some cases. */
                    if (score->legacy_oriscus_orientation) {
                        if (current_note->u.note.shape == S_ORISCUS_DESCENDENS
                                && current_note->next
                                && current_note->next->type == GRE_NOTE
                                && current_note->next->u.note.pitch <
                                current_note->u.note.pitch) {
                            last_pitch = current_note->u.note.pitch;
                            current_note->u.note.shape = S_ORISCUS_ASCENDENS;
                            current_note->u.note.liquescentia =
                                    L_NO_LIQUESCENTIA;
                            current_note = next_note;
                            continue;
                        }
                    }
                    /* special cases of the punctum inclinatum deminutus and
                     * auctus */
                    if (is_punctum_inclinatum(current_note->u.note.shape, false)) {
                        if (current_note->u.note.liquescentia == L_DEMINUTUS) {
                            current_note->u.note.shape =
                                    S_PUNCTUM_INCLINATUM_DEMINUTUS;
                        }
                        if (current_note->u.note.liquescentia
                                == L_AUCTUS_DESCENDENS
                                || current_note->u.note.liquescentia
                                == L_AUCTUS_ASCENDENS) {
                            current_note->u.note.shape =
                                    S_PUNCTUM_INCLINATUM_AUCTUS;
                        }

                        if (current_note->next
                                && current_note->next->type == GRE_NOTE
                                && is_punctum_inclinatum(
                                    current_note->next->u.note.shape, false)
                                && current_note->next->u.note.liquescentia
                                == L_DEMINUTUS) {
                            last_pitch = current_note->u.note.pitch;
                            current_note = next_note;
                            continue;
                        }
                    }
                    liquescentia |= current_note->u.note.liquescentia;
                    /* once again, only works with the good values in the header
                     * file */
                    current_note = close_glyph(&last_glyph, current_glyph_type,
                            &current_glyph_first_note, liquescentia,
                            current_note, punctum_inclinatum_orientation);
                    current_glyph_type = G_UNDETERMINED;
                    liquescentia = L_NO_LIQUESCENTIA;
                }
                break;
            case DET_END_OF_PREVIOUS:
                if (current_note->previous)
                {
                    /* we don't want to close previous glyph twice */
                    close_glyph(&last_glyph, current_glyph_type,
                            &current_glyph_first_note, liquescentia,
                            current_note->previous,
                            punctum_inclinatum_orientation);
                }
                current_glyph_type = next_glyph_type;
                liquescentia = L_NO_LIQUESCENTIA;
                last_pitch = USELESS_VALUE;
                /* we deal with liquescentia */
                if (is_tail_liquescentia(current_note->u.note.liquescentia))
                    /* not an initio debilis, because we considered it in the first
                     * part... */
                {
                    /* special cases of the punctum inclinatum deminutus and
                     * auctus */
                    if (is_punctum_inclinatum(current_note->u.note.shape, false)) {
                        if (current_note->u.note.liquescentia == L_DEMINUTUS) {
                            current_note->u.note.shape =
                                    S_PUNCTUM_INCLINATUM_DEMINUTUS;
                        }
                        if (current_note->u.note.liquescentia ==
                                L_AUCTUS_DESCENDENS
                                || current_note->u.note.liquescentia ==
                                L_AUCTUS_ASCENDENS) {
                            current_note->u.note.shape =
                                    S_PUNCTUM_INCLINATUM_AUCTUS;
                        }
                        if (current_note->next
                                && current_note->next->type == GRE_NOTE
                                && is_punctum_inclinatum(
                                    current_note->next->u.note.shape, false)
                                && current_note->next->u.note.liquescentia ==
                                L_DEMINUTUS) {
                            current_note = next_note;
                            continue;
                        }
                    }
                    current_note = close_glyph(&last_glyph, current_glyph_type,
                            &current_glyph_first_note,
                            current_note->u.note.liquescentia, current_note,
                            punctum_inclinatum_orientation);
                    current_glyph_type = G_UNDETERMINED;
                }
                break;
            case DET_END_OF_CURRENT:
                liquescentia += current_note->u.note.liquescentia;
                /* once again, only works with the good values in the header file */
                current_note = close_glyph(&last_glyph, next_glyph_type,
                        &current_glyph_first_note, liquescentia, current_note,
                        punctum_inclinatum_orientation);
                current_glyph_type = G_UNDETERMINED;
                liquescentia = L_NO_LIQUESCENTIA;
                break;
            case DET_END_OF_BOTH:
                if (current_note->previous)
                {
                    /* we don't want to close previous glyph twice */
                    close_glyph(&last_glyph, current_glyph_type,
                            &current_glyph_first_note, liquescentia,
                            current_note->previous,
                            punctum_inclinatum_orientation);
                }
                current_glyph_type = G_UNDETERMINED;
                liquescentia = L_NO_LIQUESCENTIA;
                current_note = close_glyph(&last_glyph, next_glyph_type,
                        &current_glyph_first_note,
                        current_note->u.note.liquescentia, current_note,
                        punctum_inclinatum_orientation);
                break;
            }
        }

        if ((!next_note && current_glyph_type != G_UNDETERMINED)
                || (autofuse && current_note
                    && current_note->u.note.shape == S_VIRGA)) {
            /* we must end the determination here */
            current_note = close_glyph(&last_glyph, current_glyph_type,
                    &current_glyph_first_note, liquescentia, current_note,
                    punctum_inclinatum_orientation);
            next_glyph_type = current_glyph_type = G_UNDETERMINED;
            liquescentia = L_NO_LIQUESCENTIA;
        }

        last_pitch = current_note->u.note.pitch;
        current_note = next_note;
    }
    /* end of while */

    gregorio_go_to_first_glyph(&last_glyph);
    return last_glyph;
}
