// File: VanAndelInstitute[P]intent/src/edlib.cpp
// Function: edlibAlign
EdlibAlignResult edlibAlign(const char* const queryOriginal, const int queryLength,
                                       const char* const targetOriginal, const int targetLength,
                                       const EdlibAlignConfig config) {
    EdlibAlignResult result;
    result.status = EDLIB_STATUS_OK;
    result.editDistance = -1;
    result.endLocations = result.startLocations = NULL;
    result.numLocations = 0;
    result.alignment = NULL;
    result.alignmentLength = 0;
    result.alphabetLength = 0;

    /*------------ TRANSFORM SEQUENCES AND RECOGNIZE ALPHABET -----------*/
    unsigned char* query, * target;
    string alphabet = transformSequences(queryOriginal, queryLength, targetOriginal, targetLength,
                                         &query, &target);
    result.alphabetLength = static_cast<int>(alphabet.size());
    /*-------------------------------------------------------*/

    // Handle special situation when at least one of the sequences has length 0.
    if (queryLength == 0 || targetLength == 0) {
        if (config.mode == EDLIB_MODE_NW) {
            result.editDistance = std::max(queryLength, targetLength);
            result.endLocations = static_cast<int *>(malloc(sizeof(int) * 1));
            result.endLocations[0] = targetLength - 1;
            result.numLocations = 1;
        } else if (config.mode == EDLIB_MODE_SHW || config.mode == EDLIB_MODE_HW) {
            result.editDistance = queryLength;
            result.endLocations = static_cast<int *>(malloc(sizeof(int) * 1));
            result.endLocations[0] = -1;
            result.numLocations = 1;
        } else {
            result.status = EDLIB_STATUS_ERROR;
        }

        free(query);
        free(target);
        return result;
    }

    /*--------------------- INITIALIZATION ------------------*/
    int maxNumBlocks = ceilDiv(queryLength, WORD_SIZE); // bmax in Myers
    int W = maxNumBlocks * WORD_SIZE - queryLength; // number of redundant cells in last level blocks
    EqualityDefinition equalityDefinition(alphabet, config.additionalEqualities, config.additionalEqualitiesLength);
    Word* Peq = buildPeq(static_cast<int>(alphabet.size()), query, queryLength, equalityDefinition);
    /*-------------------------------------------------------*/

    /*------------------ MAIN CALCULATION -------------------*/
    // TODO: Store alignment data only after k is determined? That could make things faster.
    int positionNW; // Used only when mode is NW.
    AlignmentData* alignData = NULL;
    bool dynamicK = false;
    int k = config.k;
    if (k < 0) { // If valid k is not given, auto-adjust k until solution is found.
        dynamicK = true;
        k = WORD_SIZE; // Gives better results than smaller k.
    }

    do {
        if (config.mode == EDLIB_MODE_HW || config.mode == EDLIB_MODE_SHW) {
            myersCalcEditDistanceSemiGlobal(Peq, W, maxNumBlocks,
                                            queryLength, target, targetLength,
                                            k, config.mode, &(result.editDistance),
                                            &(result.endLocations), &(result.numLocations));
        } else {  // mode == EDLIB_MODE_NW
            myersCalcEditDistanceNW(Peq, W, maxNumBlocks,
                                    queryLength, target, targetLength,
                                    k, &(result.editDistance), &positionNW,
                                    false, &alignData, -1);
        }
        k *= 2;
    } while(dynamicK && result.editDistance == -1);

    if (result.editDistance >= 0) {  // If there is solution.
        // If NW mode, set end location explicitly.
        if (config.mode == EDLIB_MODE_NW) {
            result.endLocations = static_cast<int *>(malloc(sizeof(int) * 1));
            result.endLocations[0] = targetLength - 1;
            result.numLocations = 1;
        }

        // Find starting locations.
        if (config.task == EDLIB_TASK_LOC || config.task == EDLIB_TASK_PATH) {
            result.startLocations = static_cast<int *>(malloc(result.numLocations * sizeof(int)));
            if (config.mode == EDLIB_MODE_HW) {  // If HW, I need to calculate start locations.
                const unsigned char* rTarget = createReverseCopy(target, targetLength);
                const unsigned char* rQuery  = createReverseCopy(query, queryLength);
                // Peq for reversed query.
                Word* rPeq = buildPeq(static_cast<int>(alphabet.size()), rQuery, queryLength, equalityDefinition);
                for (int i = 0; i < result.numLocations; i++) {
                    int endLocation = result.endLocations[i];
                    if (endLocation == -1) {
                        // NOTE: Sometimes one of optimal solutions is that query starts before target, like this:
                        //                       AAGG <- target
                        //                   CCTT     <- query
                        //   It will never be only optimal solution and it does not happen often, however it is
                        //   possible and in that case end location will be -1. What should we do with that?
                        //   Should we just skip reporting such end location, although it is a solution?
                        //   If we do report it, what is the start location? -4? -1? Nothing?
                        // TODO: Figure this out. This has to do in general with how we think about start
                        //   and end locations.
                        //   Also, we have alignment later relying on this locations to limit the space of it's
                        //   search -> how can it do it right if these locations are negative or incorrect?
                        result.startLocations[i] = 0;  // I put 0 for now, but it does not make much sense.
                    } else {
                        int bestScoreSHW, numPositionsSHW;
                        int* positionsSHW;
                        myersCalcEditDistanceSemiGlobal(
                                rPeq, W, maxNumBlocks,
                                queryLength, rTarget + targetLength - endLocation - 1, endLocation + 1,
                                result.editDistance, EDLIB_MODE_SHW,
                                &bestScoreSHW, &positionsSHW, &numPositionsSHW);
                        // Taking last location as start ensures that alignment will not start with insertions
                        // if it can start with mismatches instead.
                        result.startLocations[i] = endLocation - positionsSHW[numPositionsSHW - 1];
                        free(positionsSHW);
                    }
                }
                delete[] rTarget;
                delete[] rQuery;
                delete[] rPeq;
            } else {  // If mode is SHW or NW
                for (int i = 0; i < result.numLocations; i++) {
                    result.startLocations[i] = 0;
                }
            }
        }

        // Find alignment -> all comes down to finding alignment for NW.
        // Currently we return alignment only for first pair of locations.
        if (config.task == EDLIB_TASK_PATH) {
            int alnStartLocation = result.startLocations[0];
            int alnEndLocation = result.endLocations[0];
            const unsigned char* alnTarget = target + alnStartLocation;
            const int alnTargetLength = alnEndLocation - alnStartLocation + 1;
            const unsigned char* rAlnTarget = createReverseCopy(alnTarget, alnTargetLength);
            const unsigned char* rQuery  = createReverseCopy(query, queryLength);
            obtainAlignment(query, rQuery, queryLength,
                            alnTarget, rAlnTarget, alnTargetLength,
                            equalityDefinition, static_cast<int>(alphabet.size()), result.editDistance,
                            &(result.alignment), &(result.alignmentLength));
            delete[] rAlnTarget;
            delete[] rQuery;
        }
    }
    /*-------------------------------------------------------*/

    //--- Free memory ---//
    delete[] Peq;
    free(query);
    free(target);
    if (alignData) delete alignData;
    //-------------------//

    return result;
}

// Assembly:
pushq %rbp
movq %rsp, %rbp
pushq %r15
pushq %r14
pushq %rbx
subq $0x101e8, %rsp          # imm = 0x101E8
movq %rdi, -0x10108(%rbp)
movq %rdi, %rax
movq %rax, -0x10118(%rbp)
leaq 0x10(%rbp), %rax
movq %rax, -0x10110(%rbp)
movq %rsi, -0x20(%rbp)
movl %edx, -0x24(%rbp)
movq %rcx, -0x30(%rbp)
movl %r8d, -0x34(%rbp)
movl $0x0, (%rdi)
movl $0xffffffff, 0x4(%rdi)  # imm = 0xFFFFFFFF
movq $0x0, 0x10(%rdi)
movq $0x0, 0x8(%rdi)
movl $0x0, 0x18(%rdi)
movq $0x0, 0x20(%rdi)
movl $0x0, 0x28(%rdi)
movl $0x0, 0x2c(%rdi)
movq -0x20(%rbp), %rsi
movl -0x24(%rbp), %edx
movq -0x30(%rbp), %rcx
movl -0x34(%rbp), %r8d
leaq -0x68(%rbp), %rdi
leaq -0x40(%rbp), %r9
leaq -0x48(%rbp), %rax
movq %rax, (%rsp)
callq 0x65340
leaq -0x68(%rbp), %rdi
callq 0x8410
movq -0x10108(%rbp), %rdi
movl %eax, 0x2c(%rdi)
cmpl $0x0, -0x24(%rbp)
je 0x64b3c
cmpl $0x0, -0x34(%rbp)
jne 0x64c38
movq -0x10110(%rbp), %rax
cmpl $0x0, 0x4(%rax)
jne 0x64bb9
leaq -0x24(%rbp), %rdi
leaq -0x34(%rbp), %rsi
callq 0x68cd0
movq %rax, -0x10120(%rbp)
jmp 0x64b5f
movq -0x10108(%rbp), %rax
movq -0x10120(%rbp), %rcx
movl (%rcx), %ecx
movl %ecx, 0x4(%rax)
movl $0x4, %edi
callq 0x8770
movq %rax, %rcx
movq -0x10108(%rbp), %rax
movq %rcx, 0x8(%rax)
movl -0x34(%rbp), %edx
subl $0x1, %edx
movq 0x8(%rax), %rcx
movl %edx, (%rcx)
movl $0x1, 0x18(%rax)
jmp 0x64c1a
movq %rax, %rcx
movl %edx, %eax
movq %rcx, -0x70(%rbp)
movl %eax, -0x74(%rbp)
leaq -0x68(%rbp), %rdi
callq 0x8a98
jmp 0x65336
movq -0x10110(%rbp), %rax
cmpl $0x1, 0x4(%rax)
je 0x64bd3
movq -0x10110(%rbp), %rax
cmpl $0x2, 0x4(%rax)
jne 0x64c0b
movq -0x10108(%rbp), %rax
movl -0x24(%rbp), %ecx
movl %ecx, 0x4(%rax)
movl $0x4, %edi
callq 0x8770
movq %rax, %rcx
movq -0x10108(%rbp), %rax
movq %rcx, 0x8(%rax)
movq 0x8(%rax), %rcx
movl $0xffffffff, (%rcx)     # imm = 0xFFFFFFFF
movl $0x1, 0x18(%rax)
jmp 0x64c18
movq -0x10108(%rbp), %rax
movl $0x1, (%rax)
jmp 0x64c1a
movq -0x40(%rbp), %rdi
callq 0x86a0
movq -0x48(%rbp), %rdi
callq 0x86a0
movl $0x1, -0x78(%rbp)
jmp 0x65318
movl -0x24(%rbp), %edi
movl $0x40, %esi
callq 0x655f0
movl %eax, -0x10124(%rbp)
jmp 0x64c4d
movq -0x10110(%rbp), %rax
movl -0x10124(%rbp), %ecx
movl %ecx, -0x7c(%rbp)
movl -0x7c(%rbp), %ecx
shll $0x6, %ecx
movl -0x24(%rbp), %edx
subl %edx, %ecx
movl %ecx, -0x80(%rbp)
movq 0x10(%rax), %rdx
movl 0x18(%rax), %ecx
leaq -0x10080(%rbp), %rdi
leaq -0x68(%rbp), %rsi
callq 0x68d10
jmp 0x64c84
leaq -0x68(%rbp), %rdi
callq 0x8410
movl %eax, %edi
movq -0x40(%rbp), %rsi
movl -0x24(%rbp), %edx
leaq -0x10080(%rbp), %rcx
callq 0x65630
movq %rax, -0x10130(%rbp)
jmp 0x64cab
movq -0x10110(%rbp), %rax
movq -0x10130(%rbp), %rcx
movq %rcx, -0x10088(%rbp)
movq $0x0, -0x10098(%rbp)
movb $0x0, -0x10099(%rbp)
movl (%rax), %eax
movl %eax, -0x100a0(%rbp)
cmpl $0x0, -0x100a0(%rbp)
jge 0x64cf4
movb $0x1, -0x10099(%rbp)
movl $0x40, -0x100a0(%rbp)
jmp 0x64cf6
movq -0x10110(%rbp), %rax
cmpl $0x2, 0x4(%rax)
je 0x64d10
movq -0x10110(%rbp), %rax
cmpl $0x1, 0x4(%rax)
jne 0x64d72
movq -0x10108(%rbp), %r15
movq -0x10110(%rbp), %rax
movq -0x10088(%rbp), %rdi
movl -0x80(%rbp), %esi
movl -0x7c(%rbp), %edx
movl -0x24(%rbp), %ecx
movq -0x48(%rbp), %r8
movl -0x34(%rbp), %r9d
movl -0x100a0(%rbp), %r10d
movl 0x4(%rax), %r11d
movq %r15, %rbx
addq $0x4, %rbx
movq %r15, %r14
addq $0x8, %r14
addq $0x18, %r15
movq %rsp, %rax
movq %r15, 0x20(%rax)
movq %r14, 0x18(%rax)
movq %rbx, 0x10(%rax)
movl %r11d, 0x8(%rax)
movl %r10d, (%rax)
callq 0x657b0
jmp 0x64d70
jmp 0x64dd3
movq -0x10108(%rbp), %r11
movq -0x10088(%rbp), %rdi
movl -0x80(%rbp), %esi
movl -0x7c(%rbp), %edx
movl -0x24(%rbp), %ecx
movq -0x48(%rbp), %r8
movl -0x34(%rbp), %r9d
movl -0x100a0(%rbp), %r10d
addq $0x4, %r11
movq %rsp, %rax
leaq -0x10098(%rbp), %rbx
movq %rbx, 0x20(%rax)
leaq -0x1008c(%rbp), %rbx
movq %rbx, 0x10(%rax)
movq %r11, 0x8(%rax)
movl %r10d, (%rax)
movl $0xffffffff, 0x28(%rax) # imm = 0xFFFFFFFF
movl $0x0, 0x18(%rax)
callq 0x66170
jmp 0x64dd1
jmp 0x64dd3
movl -0x100a0(%rbp), %eax
shll %eax
movl %eax, -0x100a0(%rbp)
xorl %eax, %eax
testb $0x1, -0x10099(%rbp)
movb %al, -0x10131(%rbp)
je 0x64e06
movq -0x10108(%rbp), %rax
cmpl $-0x1, 0x4(%rax)
sete %al
movb %al, -0x10131(%rbp)
movb -0x10131(%rbp), %al
testb $0x1, %al
jne 0x64cf6
movq -0x10108(%rbp), %rax
cmpl $0x0, 0x4(%rax)
jl 0x652a2
movq -0x10110(%rbp), %rax
cmpl $0x0, 0x4(%rax)
jne 0x64e5d
movl $0x4, %edi
callq 0x8770
movq %rax, %rcx
movq -0x10108(%rbp), %rax
movq %rcx, 0x8(%rax)
movl -0x34(%rbp), %edx
subl $0x1, %edx
movq 0x8(%rax), %rcx
movl %edx, (%rcx)
movl $0x1, 0x18(%rax)
movq -0x10110(%rbp), %rax
cmpl $0x1, 0x8(%rax)
je 0x64e7b
movq -0x10110(%rbp), %rax
cmpl $0x2, 0x8(%rax)
jne 0x650fe
movq -0x10108(%rbp), %rax
movslq 0x18(%rax), %rdi
shlq $0x2, %rdi
callq 0x8770
movq -0x10108(%rbp), %rcx
movq %rax, %rdx
movq -0x10110(%rbp), %rax
movq %rdx, 0x10(%rcx)
cmpl $0x2, 0x4(%rax)
jne 0x650b4
movq -0x48(%rbp), %rdi
movl -0x34(%rbp), %esi
callq 0x66ea0
movq %rax, -0x10140(%rbp)
jmp 0x64ec3
movq -0x10140(%rbp), %rax
movq %rax, -0x100a8(%rbp)
movq -0x40(%rbp), %rdi
movl -0x24(%rbp), %esi
callq 0x66ea0
movq %rax, -0x10148(%rbp)
jmp 0x64ee6
movq -0x10148(%rbp), %rax
movq %rax, -0x100b0(%rbp)
leaq -0x68(%rbp), %rdi
callq 0x8410
movl %eax, %edi
movq -0x100b0(%rbp), %rsi
movl -0x24(%rbp), %edx
leaq -0x10080(%rbp), %rcx
callq 0x65630
movq %rax, -0x10150(%rbp)
jmp 0x64f1e
movq -0x10150(%rbp), %rax
movq %rax, -0x100b8(%rbp)
movl $0x0, -0x100bc(%rbp)
movq -0x10108(%rbp), %rcx
movl -0x100bc(%rbp), %eax
cmpl 0x18(%rcx), %eax
jge 0x65052
movq -0x10108(%rbp), %rax
movq 0x8(%rax), %rax
movslq -0x100bc(%rbp), %rcx
movl (%rax,%rcx,4), %eax
movl %eax, -0x100c0(%rbp)
cmpl $-0x1, -0x100c0(%rbp)
jne 0x64f8e
movq -0x10108(%rbp), %rax
movq 0x10(%rax), %rax
movslq -0x100bc(%rbp), %rcx
movl $0x0, (%rax,%rcx,4)
jmp 0x6503c
movq -0x10108(%rbp), %rax
movq -0x100b8(%rbp), %rdi
movl -0x80(%rbp), %esi
movl -0x7c(%rbp), %edx
movl -0x24(%rbp), %ecx
movq -0x100a8(%rbp), %r10
movslq -0x34(%rbp), %r8
addq %r8, %r10
movslq -0x100c0(%rbp), %r8
movl %r8d, %r9d
notq %r8
addq %r10, %r8
incl %r9d
movl 0x4(%rax), %r10d
movq %rsp, %rax
leaq -0x100c8(%rbp), %r11
movq %r11, 0x20(%rax)
leaq -0x100d0(%rbp), %r11
movq %r11, 0x18(%rax)
leaq -0x100c4(%rbp), %r11
movq %r11, 0x10(%rax)
movl %r10d, (%rax)
movl $0x1, 0x8(%rax)
callq 0x657b0
jmp 0x64fff
movq -0x10108(%rbp), %rax
movl -0x100c0(%rbp), %edx
movq -0x100d0(%rbp), %rcx
movl -0x100c8(%rbp), %esi
subl $0x1, %esi
movslq %esi, %rsi
subl (%rcx,%rsi,4), %edx
movq 0x10(%rax), %rax
movslq -0x100bc(%rbp), %rcx
movl %edx, (%rax,%rcx,4)
movq -0x100d0(%rbp), %rdi
callq 0x86a0
jmp 0x6503e
movl -0x100bc(%rbp), %eax
addl $0x1, %eax
movl %eax, -0x100bc(%rbp)
jmp 0x64f36
movq -0x100a8(%rbp), %rax
movq %rax, -0x10158(%rbp)
cmpq $0x0, %rax
je 0x65072
movq -0x10158(%rbp), %rdi
callq 0x8710
movq -0x100b0(%rbp), %rax
movq %rax, -0x10160(%rbp)
cmpq $0x0, %rax
je 0x65092
movq -0x10160(%rbp), %rdi
callq 0x8710
movq -0x100b8(%rbp), %rax
movq %rax, -0x10168(%rbp)
cmpq $0x0, %rax
je 0x650b2
movq -0x10168(%rbp), %rdi
callq 0x8710
jmp 0x650fc
movl $0x0, -0x100d4(%rbp)
movq -0x10108(%rbp), %rcx
movl -0x100d4(%rbp), %eax
cmpl 0x18(%rcx), %eax
jge 0x650fa
movq -0x10108(%rbp), %rax
movq 0x10(%rax), %rax
movslq -0x100d4(%rbp), %rcx
movl $0x0, (%rax,%rcx,4)
movl -0x100d4(%rbp), %eax
addl $0x1, %eax
movl %eax, -0x100d4(%rbp)
jmp 0x650be
jmp 0x650fc
jmp 0x650fe
movq -0x10110(%rbp), %rax
cmpl $0x2, 0x8(%rax)
jne 0x652a0
movq -0x10108(%rbp), %rax
movq 0x10(%rax), %rcx
movl (%rcx), %ecx
movl %ecx, -0x100d8(%rbp)
movq 0x8(%rax), %rax
movl (%rax), %eax
movl %eax, -0x100dc(%rbp)
movq -0x48(%rbp), %rax
movslq -0x100d8(%rbp), %rcx
addq %rcx, %rax
movq %rax, -0x100e8(%rbp)
movl -0x100dc(%rbp), %eax
movl -0x100d8(%rbp), %ecx
subl %ecx, %eax
incl %eax
movl %eax, -0x100ec(%rbp)
movq -0x100e8(%rbp), %rdi
movl -0x100ec(%rbp), %esi
callq 0x66ea0
movq %rax, -0x10170(%rbp)
jmp 0x65174
movq -0x10170(%rbp), %rax
movq %rax, -0x100f8(%rbp)
movq -0x40(%rbp), %rdi
movl -0x24(%rbp), %esi
callq 0x66ea0
movq %rax, -0x10178(%rbp)
jmp 0x65197
movq -0x10108(%rbp), %r14
movq -0x10178(%rbp), %rax
movq %rax, -0x10100(%rbp)
movq -0x40(%rbp), %rax
movq %rax, -0x101a8(%rbp)
movq -0x10100(%rbp), %rax
movq %rax, -0x101a0(%rbp)
movl -0x24(%rbp), %eax
movl %eax, -0x10194(%rbp)
movq -0x100e8(%rbp), %rax
movq %rax, -0x10190(%rbp)
movq -0x100f8(%rbp), %rax
movq %rax, -0x10188(%rbp)
movl -0x100ec(%rbp), %eax
movl %eax, -0x1017c(%rbp)
leaq -0x68(%rbp), %rdi
callq 0x8410
movq -0x101a8(%rbp), %rdi
movq -0x101a0(%rbp), %rsi
movl -0x10194(%rbp), %edx
movq -0x10190(%rbp), %rcx
movq -0x10188(%rbp), %r8
movl -0x1017c(%rbp), %r9d
movl %eax, %r10d
movl 0x4(%r14), %r11d
movq %r14, %rbx
addq $0x20, %rbx
addq $0x28, %r14
movq %rsp, %rax
movq %r14, 0x20(%rax)
movq %rbx, 0x18(%rax)
movl %r11d, 0x10(%rax)
movl %r10d, 0x8(%rax)
leaq -0x10080(%rbp), %r10
movq %r10, (%rax)
callq 0x66f00
jmp 0x6525e
movq -0x100f8(%rbp), %rax
movq %rax, -0x101b0(%rbp)
cmpq $0x0, %rax
je 0x6527e
movq -0x101b0(%rbp), %rdi
callq 0x8710
movq -0x10100(%rbp), %rax
movq %rax, -0x101b8(%rbp)
cmpq $0x0, %rax
je 0x6529e
movq -0x101b8(%rbp), %rdi
callq 0x8710
jmp 0x652a0
jmp 0x652a2
movq -0x10088(%rbp), %rax
movq %rax, -0x101c0(%rbp)
cmpq $0x0, %rax
je 0x652c2
movq -0x101c0(%rbp), %rdi
callq 0x8710
movq -0x40(%rbp), %rdi
callq 0x86a0
movq -0x48(%rbp), %rdi
callq 0x86a0
cmpq $0x0, -0x10098(%rbp)
je 0x65311
movq -0x10098(%rbp), %rax
movq %rax, -0x101c8(%rbp)
cmpq $0x0, %rax
je 0x6530f
movq -0x101c8(%rbp), %rdi
callq 0x68e60
movq -0x101c8(%rbp), %rdi
movl $0x28, %esi
callq 0x8560
jmp 0x65311
movl $0x1, -0x78(%rbp)
leaq -0x68(%rbp), %rdi
callq 0x8a98
movq -0x10118(%rbp), %rax
addq $0x101e8, %rsp          # imm = 0x101E8
popq %rbx
popq %r14
popq %r15
popq %rbp
retq
movq -0x70(%rbp), %rdi
callq 0x8970
nop