/*
 * mus.c -- Midi Wavetable Processing library
 *
 * Copyright (C) WildMIDI Developers 2001-2016
 *
 * This file is part of WildMIDI.
 *
 * WildMIDI is free software: you can redistribute and/or modify the player
 * under the terms of the GNU General Public License and you can redistribute
 * and/or modify the library under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation, either version 3 of
 * the licenses, or(at your option) any later version.
 *
 * WildMIDI 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 and
 * the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License and the
 * GNU Lesser General Public License along with WildMIDI.  If not,  see
 * <http://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "common.h"
#include "wm_error.h"
#include "wildmidi_lib.h"
#include "internal_midi.h"
#include "reverb.h"
#include "f_mus.h"

#ifdef DEBUG_MUS
#define MUS_EVENT_DEBUG(dx,dy,dz) fprintf(stderr,"\r%s, 0x%.2x, 0x%.8x\n",dx,dy,dz)
#else
#define MUS_EVENT_DEBUG(dx,dy,dz)
#endif

/*
 Turns mus file data into an event stream.
 */
struct _mdi *
_WM_ParseNewMus(const uint8_t *mus_data, uint32_t mus_size) {
    uint8_t mus_hdr[] = { 'M', 'U', 'S', 0x1A };
    uint32_t mus_song_ofs = 0;
    uint32_t mus_song_len = 0;
    uint16_t mus_ch_cnt1 = 0;
    uint16_t mus_ch_cnt2 = 0;
    uint16_t mus_no_instr = 0;
    uint32_t mus_data_ofs = 0;
    uint16_t * mus_mid_instr = NULL;
    uint16_t mus_instr_cnt = 0;
    struct _mdi *mus_mdi;
    uint32_t mus_divisions = 60;
    float tempo_f = 0;
    uint16_t mus_freq = 0;
    float samples_per_tick_f = 0;
#define MUS_SZ 4
    uint8_t mus_event[MUS_SZ] = { 0, 0, 0, 0 };
    uint8_t mus_event_size = 0;
    uint8_t mus_prev_vol[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
    uint32_t setup_ret = 0;
    uint32_t mus_ticks = 0;
    uint32_t sample_count = 0;
    float sample_count_f = 0;
    float sample_remainder = 0;
    uint16_t pitchbend_tmp = 0;

    if (mus_size < 18) {
        _WM_GLOBAL_ERROR(WM_ERR_CORUPT, "file too short", 0);
        return NULL;
    }

    if (memcmp(mus_data, mus_hdr, 4)) {
        _WM_GLOBAL_ERROR(WM_ERR_NOT_MUS, NULL, 0);
        return NULL;
    }

    /* Get Song Length */
    mus_song_len = (mus_data[5] << 8) | mus_data[4];
    /* Get Song Offset */
    mus_song_ofs = (mus_data[7] << 8) | mus_data[6];

    /* Have yet to determine what this actually is. */
    mus_ch_cnt1 = (mus_data[9] << 8) | mus_data[8];
    mus_ch_cnt2 = (mus_data[11] << 8) | mus_data[10];

    WMIDI_UNUSED(mus_ch_cnt1);
    WMIDI_UNUSED(mus_ch_cnt2);

    /* Number of instruments defined */
    mus_no_instr = (mus_data[13] << 8) | mus_data[12];

    /* Skip next 2 data bytes */
    mus_data_ofs = 16;

    /* Check that we have enough data to check the rest */
    if (mus_size < (mus_data_ofs + (mus_no_instr << 1) + mus_song_len)) {
        _WM_GLOBAL_ERROR(WM_ERR_CORUPT, "file too short", 0);
        return NULL;
    }

    /* GHSA-v6gx-9vh4-c8qx: mus_song_ofs is a file-controlled uint16 used as
     * a raw index into mus_data. Bound it against mus_size with room for
     * the 3-byte first event read below. */
    if (mus_song_ofs > mus_size - 3) {
        _WM_GLOBAL_ERROR(WM_ERR_CORUPT, "file too short", 0);
        return NULL;
    }

    /* Instrument definition */
    mus_mid_instr = (uint16_t *) malloc(mus_no_instr * sizeof(uint16_t));
    for (mus_instr_cnt = 0; mus_instr_cnt < mus_no_instr; mus_instr_cnt++) {
        mus_mid_instr[mus_instr_cnt] = (mus_data[mus_data_ofs + 1] << 8) | mus_data[mus_data_ofs];
        mus_data_ofs += 2;
    }

    /* make sure we are at song offset */
    mus_data_ofs = mus_song_ofs;

    /* do some calculations so we know how many samples per mus tick */
    mus_freq = _cvt_get_option(WM_CO_FREQUENCY);
    if (mus_freq == 0) mus_freq = 140;

    if ((_WM_MixerOptions & WM_MO_ROUNDTEMPO)) {
        tempo_f = (float) (60000000 / mus_freq) + 0.5f;
    } else {
        tempo_f = (float) (60000000 / mus_freq);
    }

    samples_per_tick_f = _WM_GetSamplesPerTick(mus_divisions, (uint32_t)tempo_f);

    /* initialise the mdi structure */
    mus_mdi = _WM_initMDI();
    _WM_midi_setup_divisions(mus_mdi, mus_divisions);
    _WM_midi_setup_tempo(mus_mdi, (uint32_t)tempo_f);

    /* lets do this */
    do {
        unsigned int channel;

         /* Build the event */
        _mus_build_event:

        channel = mus_data[mus_data_ofs] & 0x0f;

        /* Mus drums happen on channel 15, swap channel 9 & 15 */
        /* DEBUG */
        MUS_EVENT_DEBUG("Before", mus_data[mus_data_ofs] & 0xf0 | channel, 0);

        if (channel == 0x0f) {
            channel = 0x09;
        } else if (channel == 0x09) {
            channel = 0x0f;
        }

        /* DEBUG */
        MUS_EVENT_DEBUG("After", mus_data[mus_data_ofs] & 0xf0 | channel, 0);

        switch ((mus_data[mus_data_ofs] >> 4) & 0x07) {
            case 0: /* Note Off */
                mus_event_size = 2;
                mus_event[0] = 0x80 | channel;
                mus_event[1] = mus_data[mus_data_ofs + 1];
                mus_event[2] = 0;
                mus_event[3] = 0;
                break;
            case 1: /* Note On */
                if (mus_data[mus_data_ofs + 1] & 0x80) {
                    mus_event_size = 3;
                    mus_event[0] = 0x90 | channel;
                    mus_event[1] = mus_data[mus_data_ofs + 1] & 0x7f;
                    mus_event[2] = mus_data[mus_data_ofs + 2];
                    /* The maximum volume is 127, but it is encoded as
                       a byte. Some songs erroneously use values higher
                       than 127, so we have to clamp them down.
                       https://github.com/Mindwerks/wildmidi/pull/226 */
                    if (mus_event[2] > 0x7f) mus_event[2] = 0x7f;
                    mus_event[3] = 0;
                    mus_prev_vol[channel] = mus_event[2];
                } else {
                    mus_event_size = 2;
                    mus_event[0] = 0x90 | channel;
                    mus_event[1] = mus_data[mus_data_ofs + 1];
                    mus_event[2] = mus_prev_vol[channel];
                    mus_event[3] = 0;
                }
                break;
            case 2: /* Pitch Bend */
                mus_event_size = 2;
                mus_event[0] = 0xe0 | channel;

                pitchbend_tmp = mus_data[mus_data_ofs + 1] << 6;
                mus_event[1] = pitchbend_tmp & 0x7f;
                mus_event[2] = (pitchbend_tmp >> 7) & 0x7f;
                mus_event[3] = 0;
                break;
            case 3:
                mus_event_size = 2;
                switch (mus_data[mus_data_ofs + 1]) {
                    case 10: /* All Sounds Off */
                        mus_event[0] = 0xb0 | channel;
                        mus_event[1] = 120;
                        mus_event[2] = 0;
                        mus_event[3] = 0;
                        break;
                    case 11: /* All Notes Off */
                        mus_event[0] = 0xb0 | channel;
                        mus_event[1] = 123;
                        mus_event[2] = 0;
                        mus_event[3] = 0;
                        break;
                    case 12: /* Mono (Not supported by WildMIDI) */
                        /*
                         **************************
                         FIXME: Add dummy mdi event
                         **************************
                         */
                        mus_event[0] = 0xb0 | channel;
                        mus_event[1] = 126;
                        mus_event[2] = 0;
                        mus_event[3] = 0;
                        break;
                    case 13: /* Poly (Not supported by WildMIDI) */
                        /*
                         **************************
                         FIXME: Add dummy mdi event
                         **************************
                         */
                        mus_event[0] = 0xb0 | channel;
                        mus_event[1] = 127;
                        mus_event[2] = 0;
                        mus_event[3] = 0;
                        break;
                    case 14: /* Reset All Controllers */
                        mus_event[0] = 0xb0 | channel;
                        mus_event[1] = 121;
                        mus_event[2] = 0;
                        mus_event[3] = 0;
                        break;
                    default: /* Unsupported */
                        goto _mus_next_data;
                }
                break;
            case 4:
                mus_event_size = 3;
                switch (mus_data[mus_data_ofs + 1]) {
                    case 0: /* Patch */
                        /*
                         *************************************************
                         FIXME: Check if setting is MIDI or MUS instrument
                         *************************************************
                         */
                        mus_event[0] = 0xc0 | channel;
                        mus_event[1] = mus_data[mus_data_ofs + 2];
                        mus_event[2] = 0;
                        mus_event[3] = 0;
                        break;
                    case 1: /* Bank Select */
                        mus_event[0] = 0xb0 | channel;
                        mus_event[1] = 0;
                        mus_event[2] = mus_data[mus_data_ofs + 2];
                        mus_event[3] = 0;
                        break;
                    case 2: /* Modulation (Not supported by WildMidi) */
                        mus_event[0] = 0xb0 | channel;
                        mus_event[1] = 1;
                        mus_event[2] = mus_data[mus_data_ofs + 2];
                        mus_event[3] = 0;
                        break;
                    case 3: /* Volume */
                        mus_event[0] = 0xb0 | channel;
                        mus_event[1] = 7;
                        mus_event[2] = mus_data[mus_data_ofs + 2];
                        /* The maximum volume is 127, but it is encoded as
                           a byte. Some songs erroneously use values higher
                           than 127, so we have to clamp them down.
                           https://github.com/Mindwerks/wildmidi/pull/226 */
                        if (mus_event[2] > 0x7f) mus_event[2] = 0x7f;
                        mus_event[3] = 0;
                        break;
                    case 4: /* Pan */
                        mus_event[0] = 0xb0 | channel;
                        mus_event[1] = 10;
                        mus_event[2] = mus_data[mus_data_ofs + 2];
                        mus_event[3] = 0;
                        break;
                    case 5: /* Expression */
                        mus_event[0] = 0xb0 | channel;
                        mus_event[1] = 11;
                        mus_event[2] = mus_data[mus_data_ofs + 2];
                        mus_event[3] = 0;
                        break;
                    case 6: /* Reverb (Not supported by WildMidi) */
                        mus_event[0] = 0xb0 | channel;
                        mus_event[1] = 91;
                        mus_event[2] = mus_data[mus_data_ofs + 2];
                        mus_event[3] = 0;
                        break;
                    case 7: /* Chorus (Not supported by WildMidi) */
                        mus_event[0] = 0xb0 | channel;
                        mus_event[1] = 93;
                        mus_event[2] = mus_data[mus_data_ofs + 2];
                        mus_event[3] = 0;
                        break;
                    case 8: /* Sustain */
                        mus_event[0] = 0xb0 | channel;
                        mus_event[1] = 64;
                        mus_event[2] = mus_data[mus_data_ofs + 2];
                        mus_event[3] = 0;
                        break;
                    case 9: /* Soft Peddle (Not supported by WildMidi) */
                        mus_event[0] = 0xb0 | channel;
                        mus_event[1] = 67;
                        mus_event[2] = mus_data[mus_data_ofs + 2];
                        mus_event[3] = 0;
                        break;
                    default: /* Unsupported */
                        goto _mus_next_data;
                }
                break;
            case 5:
                mus_event_size = 1;
                goto _mus_next_data;
            case 6:
                goto _mus_end_of_song;
            case 7:
                mus_event_size = 1;
                goto _mus_next_data;
        }

        setup_ret = _WM_SetupMidiEvent(mus_mdi, (uint8_t *)mus_event, MUS_SZ, 0);
        if (setup_ret == 0) {
            goto _mus_end;
        }

    _mus_next_data:
        if (!(mus_data[mus_data_ofs] & 0x80)) {
            mus_data_ofs += mus_event_size;
            goto _mus_build_event;

        }
        mus_data_ofs += mus_event_size;

        mus_ticks = 0;
        do {
            mus_ticks = (mus_ticks << 7) | (mus_data[mus_data_ofs++] & 0x7f);
        } while (mus_data[mus_data_ofs - 1] & 0x80);

        sample_count_f = ((float)mus_ticks * samples_per_tick_f) + sample_remainder;
        sample_count = (uint32_t)sample_count_f;
        sample_remainder = sample_count_f - (float)sample_count;

        mus_mdi->events[mus_mdi->event_count - 1].samples_to_next = sample_count;
        mus_mdi->extra_info.approx_total_samples += sample_count;

    } while (mus_data_ofs < mus_size);

_mus_end_of_song:
    /* Finalise mdi structure */
    if ((mus_mdi->reverb = _WM_init_reverb(_WM_SampleRate, _WM_reverb_room_width, _WM_reverb_room_length, _WM_reverb_listen_posx, _WM_reverb_listen_posy)) == NULL) {
        _WM_GLOBAL_ERROR(WM_ERR_MEM, NULL, 0);
        goto _mus_end;
    }
    _WM_midi_setup_endoftrack(mus_mdi);
    mus_mdi->extra_info.current_sample = 0;
    mus_mdi->current_event = &mus_mdi->events[0];
    mus_mdi->samples_to_mix = 0;
    mus_mdi->note = NULL;

    _WM_ResetToStart(mus_mdi);

_mus_end:
    free(mus_mid_instr);
    if (mus_mdi->reverb) return (mus_mdi);
    _WM_freeMDI(mus_mdi);
    return NULL;
}
