Source: routers/sheetMusicPart.js

const express = require('express');
const auth = require('../middleware/authenticator.js');
const verify = require("../middleware/verifier.js");
const mysql = require("../db/mysql.js");
const alphaProcesser = require('../AlphaTab/ProcessAlphaTab.js');
const Manipulator = require("../Parser/ParsedOutputManipulator.js");
const ParserPData = require("../Parser/ParserPerformanceData.js");
const ParserMeasures = require("../Parser/ParserMeasures.js");
const updateExerciseTable = require("../util/updateExercise.js");
const router = express.Router();

/**
 * @typedef {object} SheetMusicPartGetPackage
 * @property {string} sheet_music The created AlphaTex isolating the user's part
 * @property {string[]} part_list A list of part (i.e. track) names in the generated AlphaTex
 * @property {string[]} clefs Clefs per staff
 * @property {number[]} performance_expectation A 1D array of even size with the ith index as the midi value and the i+1 index as the duration of that note for i%2==0
 * @property {number[]} lower_upper A 1D two valued array with the lower and upper midi values
 * @property {number[]} measure_lengths A collection of the lengths of each Measure. The ith index contains the length in seconds of the i+1 Measure
 * @property {string} exerciseId The ID of the exercise created
 */

/**
 * Route for getting only the user's part from the given sheet music. Creates AlphaTex with their part isolated
 * @name sheet-music-part/get
 * @function
 * @inner
 * @param {*} req
 * @param {string} req.query.sheetMusicId The sheet music ID to be accessed
 * @param {*} res
 * @returns {SheetMusicPartGetPackage|string} The created isolated sheet music and information about it. Otherwise, an error message if an error occured
 * @async
 */
router.get('/', auth.token, async (req,res) => {
    verify.memberOfSheetMusic(req, res).then((choirId) => {
        if (choirId === null) {
            res.status(403).send('Unauthorized');
        } else {
            let sheetMusicId = req.query.sheetMusicId;
            const errorMessage = "Unable to get single part from sheet music";
            if (sheetMusicId) {
                mysql.getClient().then((client) => {
                    client.query("SELECT hex(member_id) AS member_id FROM tma.member WHERE person_id=? AND choir_id=unhex(?)", [res.locals.uid, choirId], function(error, results, fields) {
                        if (error) {
                            res.status(503).send(errorMessage);
                        } else if (results.length === 0) {
                            res.status(204).send(errorMessage);
                        } else {
                            let memberId = results[0]["member_id"];
                            client.query("SELECT part FROM tma.member_sheet_music_part WHERE member_id=unhex(?) AND sheet_music_id=unhex(?)", [memberId, sheetMusicId], function(error, results, fields) {
                                if (error) {
                                    res.status(503).send(errorMessage);
                                } else if (results.length === 0) {
                                    res.status(204).send(errorMessage);
                                } else {
                                    let partName = results[0]["part"];
                                    client.query("SELECT performance_data FROM tma.sheet_music WHERE sheet_music_id = unhex(?)", [sheetMusicId], function(error, results, fields) {
                                        if (error) {
                                            res.status(503).send(errorMessage);
                                        } else {
                                            let performanceData = JSON.parse(results[0]['performance_data']);
                                            const measureParser = new ParserMeasures(performanceData);
                                            let measureEnd = measureParser.manipulator.getMeasureEnd(partName, 1);
    
                                            let trackIndex = measureParser.manipulator.getTrackIndexByName(partName);
    
                                            if (measureEnd === null) {
                                                res.status(400).send(errorMessage);
                                            } else {
                                                let {lyricText, alphaTex} = measureParser.measuresToAlphaTex([1, measureEnd, "all"], trackIndex + 1, 1, false);
    
                                                let exercisePerformanceData = alphaProcesser.process(alphaTex, {0:lyricText});
                                                let partNames = ParserPData.getPartNames(exercisePerformanceData.mainObj);
                                                let clefs = ParserPData.getClefs(exercisePerformanceData.mainObj);
    
                                                let noteStream = ParserPData.getNoteStream(exercisePerformanceData.mainObj, partName);
                                                let lowerAndUpper = ParserPData.getLowerAndUpper(noteStream);
                                                let manipulator = new Manipulator(exercisePerformanceData.mainObj);
                                                let measureLengths = manipulator.getMeasureTimeLength(partName);
    
                                                let retObj = {
                                                    "sheet_music": alphaTex,
                                                    "part_list": partNames,
                                                    clefs,
                                                    "performance_expectation": noteStream,
                                                    "lower_upper": lowerAndUpper,
                                                    "measure_lengths": measureLengths,
                                                };
    
                                                updateExerciseTable(sheetMusicId, trackIndex + 1, 0, 1, measureEnd, false).then((exerciseId) => {
                                                    retObj.exerciseId = exerciseId;
                                                    res.status(200).json(retObj);
                                                });
                                            }
                                        }
                                    });
                                }
                            });
                        }
                    });
                });
            } else {
                res.status(400).send(errorMessage);
            }
        }
    }).catch((error) => {
        res.status(403).send('Unauthorized');
    });
});

/**
 * Route for assigning a part to a user for a piece of sheet music
 * @name sheet-music-part/post
 * @function
 * @inner
 * @param {*} req
 * @param {string} req.body.memberId The member ID to be accessed
 * @param {string} req.body.sheetMusicId The sheet music ID for the part
 * @param {string} req.body.part The part to be assigned to the member
 * @param {*} res
 * @returns {string} An error message if an error occured
 * @async
 */
router.post('/', auth.token, async (req,res) => {
    verify.userIsMember(req, res).then((isMember) => {
        if (!isMember) {
            res.status(403).send('Unauthorized');
        } else {
            verify.memberOfSheetMusic(req, res).then((choirId) => {
                if (choirId === null) {
                    res.status(403).send('Unauthorized');
                } else {
                    let sheetMusicId = req.body.sheetMusicId;
                    let part = req.body.part;
                    let memberId = req.body.memberId;
                    const errorMessage = "Unable to add part for member in sheet music";
                    if (sheetMusicId && part && memberId) {
                        mysql.getClient().then((client) => {
                            client.beginTransaction(function(error) {
                                client.query("INSERT INTO tma.member_sheet_music_part (member_id, sheet_music_id, part) values(unhex(?), unhex(?), ?)", [memberId, sheetMusicId, part], function(error, results, fields) {
                                    if (error) {
                                        res.status(503).send(errorMessage);
                                    } else {
                                        client.query("SELECT performance_data FROM tma.sheet_music WHERE sheet_music_id = unhex(?)", [sheetMusicId], function(error, results, fields) {
                                            if (error) {
                                                client.rollback(function() {
                                                    res.status(503).send(errorMessage);
                                                });
                                            } else {
                                                let performanceData = JSON.parse(results[0]['performance_data']);
                                                let {progressData, trackIndex} = ParserPData.createEmptyProgressData(performanceData, part);
                                                let progress = {
                                                    index: 0,
                                                    data: [progressData]
                                                };
                                                client.query("INSERT INTO tma.performance_progress (performance_progress_id, member_id, sheet_music_id, staff_number, progress_data) values(unhex(replace(uuid(),\"-\",\"\")), unhex(?), unhex(?), ?, ?)", [memberId, sheetMusicId, trackIndex+1, JSON.stringify(progress)], function(error, results, fields) {
                                                    if (error) {
                                                        client.rollback(function() {
                                                            res.status(503).send(errorMessage);
                                                        });
                                                    } else {
                                                        client.commit(function(err) {
                                                            if (err) {
                                                                client.rollback(function() {
                                                                    res.status(503).send(errorMessage);
                                                                });
                                                            } else {
                                                                res.status(200).send();
                                                            }
                                                        });
                                                    }
                                                });
                                            }
                                        });
                                    }
                                });
                            });
                        });
                    } else {
                        res.status(400).send(errorMessage);
                    }
                }
            }).catch((error) => {
                res.status(403).send('Unauthorized');
            });
        }
    }).catch((error) => {
        res.status(403).send('Unauthorized');
    });
});

module.exports = router;