/*
 * electronic_piano_tasks.cpp
 *
 *  Created on: Nov 5, 2015
 *      Author: Jason
 */
#include "electronic_piano_tasks.hpp"

/* === MIDI_TASK =================================================================================================== */

bool Midi_task::init( void ) {
    My_spi::init_cs( MIDI_RECEIVER_PORT, MIDI_RECEIVER_PIN );
    QueueHandle_t command_queue = xQueueCreate( COMMAND_QUEUE_SIZE, sizeof( byte_t ) );
    QueueHandle_t note_queue = xQueueCreate( NOTE_QUEUE_SIZE, sizeof( byte_t ) );
    QueueHandle_t velocity_queue = xQueueCreate( NOTE_QUEUE_SIZE, sizeof( byte_t ) );
    addSharedObject ( COMMAND_QUEUE_ID, command_queue );
    addSharedObject ( NOTE_QUEUE_ID, note_queue );
    addSharedObject ( VELOCITY_QUEUE_ID, velocity_queue );
    note_base = DEFAULT_NOTE_BASE;
    note_scope_index = DEFAULT_NOTE_SCOPE_INDEX + '0';
    LD.setRightDigit( note_scope_index );
    LD.setLeftDigit( '0' );
    current_vol_index = VOL_MAX_INDEX;             // On reset: default volume index (MAX)
    change_vol_led( current_vol_index );
    return true;
}

bool Midi_task::run( void *p ) {
    bool queue_not_empty = true;
    struct midi_msg_t midi_msg;
    byte_t midi_command = 0;
    xQueueReceive( getSharedObject( COMMAND_QUEUE_ID ), &midi_command, portMAX_DELAY );
    if ( midi_command == NOTE_ON_BASE ) {
        while ( queue_not_empty ) {
            uint8_t key_id = 0;
            uint8_t key_velocity = 0;
            queue_not_empty = xQueueReceive( getSharedObject( NOTE_QUEUE_ID ), &key_id, 0 );
            if ( queue_not_empty ) {
                xQueueReceive( getSharedObject( VELOCITY_QUEUE_ID ), &key_velocity, 0 );
                midi_command = key_velocity ? NOTE_ON_BASE : NOTE_OFF_BASE;
                build_midi_msg( &midi_msg, midi_command, note_base + key_id, key_velocity );
                send_midi_msg ( &midi_msg );
            }
        }
    } // END if midi_command == NOTE_ON_BASE
    else if ( midi_command == VOL_UP || midi_command == VOL_DOWN ) {
        enum volume target_vol = MAX;
        bool note_changed = false;
        switch ( midi_command ) {
            case VOL_UP : note_changed = change_vol( INCREMENT ); break;
            case VOL_DOWN : note_changed = change_vol( DECREMENT ); break;
        }
        switch ( current_vol_index ) {
            case 4 : target_vol = MAX; break;
            case 3 : target_vol = HIGH; break;
            case 2 : target_vol = MID; break;
            case 1 : target_vol = LOW; break;
            case 0 : target_vol = OFF; break;
            default : target_vol = OFF; break;
        }
        if ( note_changed ) {
            change_vol_led( current_vol_index );
            build_midi_msg( &midi_msg, MIDI_CON_BASE, MIDI_CON_VOL, target_vol );
            send_midi_msg ( &midi_msg );
        }
    } // End else if midi_command == VOL_UP || midi_command == VOL_DOWN
    else if ( midi_command == DEC_NOTE_SCOPE || midi_command == INC_NOTE_SCOPE ) {
        switch ( midi_command ) {
            case INC_NOTE_SCOPE : change_note_base( INCREMENT ); break;
            case DEC_NOTE_SCOPE : change_note_base( DECREMENT ); break;
        }
    } // END else if midi_command == DEC_NOTE_SCOPE || midi_command == INC_NOTE_SCOPE
    return true;
}

inline void Midi_task::build_midi_msg( struct midi_msg_t *midi_msg, byte_t command, byte_t field_1, byte_t field_2 ) {
    midi_msg->status = command;
    midi_msg->data_1 = field_1;
    midi_msg->data_2 = field_2;
}

void Midi_task::send_midi_msg( struct midi_msg_t *midi_msg ) {
    /* Determine # of fields in midi msg: (MIDI_CON_BASE <= status >= MIDI_CON_MAX) OR status = MIDI_INSTRUMENT_CON means 2 fields, else 3 fields */
    const uint8_t msg_size = ( midi_msg->status == MIDI_INSTRUMENT_CON ) ? 2 : 3;
    /* Via SPI, send midi msg with 1 byte padding for each field; padding order determined by PAD_FIRST */
    My_spi::cs_select( MIDI_RECEIVER_PORT, MIDI_RECEIVER_PIN );
    for ( uint8_t msg_index = 0; msg_index < msg_size; msg_index++ ) {
        if ( PAD_FIRST ) My_spi::spi_exchange_byte( BYTE_PADDING );
        switch (msg_index) {
            case 0 : My_spi::spi_exchange_byte( midi_msg->status ); break;
            case 1 : My_spi::spi_exchange_byte( midi_msg->data_1 ); break;
            case 2 : My_spi::spi_exchange_byte( midi_msg->data_2 ); break;
        }
        if ( !PAD_FIRST ) My_spi::spi_exchange_byte( BYTE_PADDING );
    }
    My_spi::cs_deselect( MIDI_RECEIVER_PORT, MIDI_RECEIVER_PIN );
}

void Midi_task::change_note_base( uint8_t direction ) {
    const uint8_t scope_of_notes = KEY_COUNT;
    if ( direction == INCREMENT && note_base < MAX_NOTE_BASE ) {
        note_base += scope_of_notes;
        note_scope_index++;
        LD.setRightDigit( note_scope_index );
    }
    else if ( direction == DECREMENT && note_base > MIN_NOTE_BASE ) {
        note_base -= scope_of_notes;
        note_scope_index--;
        LD.setRightDigit( note_scope_index );
    }
}

bool Midi_task::change_vol( uint8_t direction ) {
    bool vol_changed = false;
    if ( direction == INCREMENT && current_vol_index < VOL_MAX_INDEX ) {
        current_vol_index++;
        vol_changed = true;
    }
    else if ( direction == DECREMENT && current_vol_index > VOL_MIN_INDEX ) {
        current_vol_index--;
        vol_changed = true;
    }
    return vol_changed;
}

void Midi_task::change_vol_led( uint8_t level ) {
    switch ( level ) {
        case 0 : LE.setAll(0x0); break;
        case 1 : LE.setAll(0x1); break;
        case 2 : LE.setAll(0x3); break;
        case 3 : LE.setAll(0x7); break;
        case 4 : LE.setAll(0xF); break;
        default : LE.setAll(0x0); break;
    }
}

/* === IR_KEY_TASK ================================================================================================= */

bool Ir_key_task::init( void ) {
    /* Init SPI from My_spi */
    My_spi::init_ssp1_spi();
    /* Init ir_keys array; assemble object and store into array */
    for ( uint8_t index = 0; index < KEY_COUNT; index++ ) {
        struct key_t ir_key;
        uint8_t key_note_map[KEY_COUNT] = { KEY_0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5,
                                            KEY_6, KEY_7, KEY_8, KEY_9, KEY_10, KEY_11 };
        ir_key.id = index;
        ir_key.velocity = 0;
        ir_key.count = 0;
        ir_key.note = key_note_map[index];
        ir_key.active = false;
        ir_keys[index] = ir_key;
    }
    /* Init adc_cs_list array; assemble object and store into array */
    for ( uint8_t index = 0; index < ADC_COUNT; index++ ) {
        struct cs_t adc_cs;
        switch ( index ) {
            case 0 :  adc_cs.port = ADC_0_PORT; adc_cs.pin = ADC_0_PIN; break;
            case 1 :  adc_cs.port = ADC_1_PORT; adc_cs.pin = ADC_1_PIN; break;
            case 2 :  adc_cs.port = ADC_2_PORT; adc_cs.pin = ADC_2_PIN; break;
            case 3 :  adc_cs.port = ADC_3_PORT; adc_cs.pin = ADC_3_PIN; break;
            case 4 :  adc_cs.port = ADC_4_PORT; adc_cs.pin = ADC_4_PIN; break;
            case 5 :  adc_cs.port = ADC_5_PORT; adc_cs.pin = ADC_5_PIN; break;
            default : adc_cs.port = 0; adc_cs.pin = 0; break;
        }
        adc_cs_list[index] = adc_cs;
    }
    /* Init CS */
    for ( uint8_t cs_index = 0; cs_index < ADC_COUNT; cs_index++ ) {
        My_spi::init_cs( adc_cs_list[cs_index].port, adc_cs_list[cs_index].pin );
    }
    /* Init sample rate */
    setRunDuration( IR_SAMPLE_RATE_MS );
    return true;
}

bool Ir_key_task::run( void *p ) {
    read_keys( ir_keys, adc_cs_list );
    process_keys( ir_keys );
    return true;
}

/* Using 2-channel ADC; therefore, read 2 channels per ADC */
void Ir_key_task::read_keys( struct key_t *keys, struct cs_t *cs_list ) {
    uint8_t key_index = 0;
    const uint8_t ch_0 = 0;
    const uint8_t ch_1 = 1;
    for ( uint8_t adc_index = 0; adc_index < ADC_COUNT; adc_index++ ) {
        cs_t *p_cs = &cs_list[adc_index];
        keys[key_index++].velocity = read_adc( p_cs, ch_0 );
        keys[key_index++].velocity = read_adc( p_cs, ch_1 );
    }
}

uint16_t Ir_key_task::read_adc( cs_t *cs, uint8_t ch ) {
    uint8_t hi = 0;
    uint8_t lo = 0;
    uint16_t velocity = 0;
    byte_t cmd = READ_CH0;
    switch ( ch ) {
        case 0 : cmd = READ_CH0; break;
        case 1 : cmd = READ_CH1; break;
        default : cmd = READ_CH0; break;
    }
    My_spi::cs_select( cs->port, cs->pin );
    hi = My_spi::spi_exchange_byte( cmd );
    lo = My_spi::spi_exchange_byte( NOT_USED );
    My_spi::cs_deselect( cs->port, cs->pin );
    velocity = ( hi & ~(0xF0) ) << 8;   // Assign upper word, but zero out the upper nibble first
    velocity |= lo;                     // Include lower word
    return velocity;
}

void Ir_key_task::process_keys( struct key_t *keys ) {
    bool send_cmd = false;
    byte_t command = NOTE_ON_BASE;
    byte_t note_on = MAX_VELOCITY;
    byte_t note_off = 0x00;
    for ( uint8_t key_index = 0; key_index < KEY_COUNT; key_index++ ) {
        key_t *p_target_key = &keys[key_index];
        switch ( p_target_key->active ) {
            case false :
                if ( p_target_key->velocity >= NOTE_ON_LOWER_LIM ) {
                    if ( p_target_key->count == IR_SAMPLE_COUNT ) {
                        xQueueSend( getSharedObject( NOTE_QUEUE_ID ), &p_target_key->note, portMAX_DELAY );
                        xQueueSend( getSharedObject( VELOCITY_QUEUE_ID ), &note_on, portMAX_DELAY );
                        p_target_key->active = true;
                        send_cmd = true;
                    }
                    else {
                        p_target_key->count++;
                    }
                }
                else {
                    p_target_key->count = 0;
                }
                break;
            case true :
                if ( p_target_key->velocity <= NOTE_OFF_UPPER_LIM ) {
#if EN_NOTE_OFF
                    xQueueSend( getSharedObject( NOTE_QUEUE_ID ), &p_target_key->note, portMAX_DELAY );
                    xQueueSend( getSharedObject( VELOCITY_QUEUE_ID ), &note_off, portMAX_DELAY );
#endif
                    p_target_key->active = false;
                    send_cmd = true;
                    p_target_key->count = 0;
                }
                break;
        }
    }
    if ( send_cmd ) {
        xQueueSend( getSharedObject ( COMMAND_QUEUE_ID ), &command, portMAX_DELAY );
    }
}

/* === CONTROL_TASK ================================================================================================ */

bool Control_task::init( void ) {
    /* Using onboard btn: P1.9, P1.10, P1.14, P1.15 */
    LPC_PINCON->PINSEL2 &= ~( ( 3 << 18 ) || ( 3 << 20 ) || ( 3 << 28 ) || ( 3 << 30 ) );
    My_gpio::set_gpio_mode( NOTE_UP_PORT, NOTE_UP_PIN, INPUT );
    My_gpio::set_gpio_mode( NOTE_DOWN_PORT, NOTE_DOWN_PIN, INPUT );
    My_gpio::set_gpio_mode( VOL_UP_PORT, VOL_UP_PIN, INPUT );
    My_gpio::set_gpio_mode( VOL_DOWN_PORT, VOL_DOWN_PIN, INPUT );
    setRunDuration( INPUT_SAMPLE_RATE_MS );
    return true;
}

bool Control_task::run( void *p ) {
    byte_t command = 0x00;
    uint8_t btn_delay = 200;        // Push Button delay after activation / [ms]
    if ( My_gpio::digital_read( NOTE_UP_PORT, NOTE_UP_PIN ) ) {
        command = INC_NOTE_SCOPE;
        xQueueSend( getSharedObject( COMMAND_QUEUE_ID ), &command, portMAX_DELAY );
        vTaskDelay(btn_delay);
    }

    else if ( My_gpio::digital_read( NOTE_DOWN_PORT, NOTE_DOWN_PIN ) ) {
        command = DEC_NOTE_SCOPE;
        xQueueSend( getSharedObject( COMMAND_QUEUE_ID ), &command, portMAX_DELAY );
        vTaskDelay(btn_delay);
    }
    else if ( My_gpio::digital_read( VOL_UP_PORT, VOL_UP_PIN ) ) {
        command = VOL_UP;
        xQueueSend( getSharedObject( COMMAND_QUEUE_ID ), &command, portMAX_DELAY );
        vTaskDelay(btn_delay);
    }
    else if ( My_gpio::digital_read( VOL_DOWN_PORT, VOL_DOWN_PIN ) ) {
        command = VOL_DOWN;
        xQueueSend( getSharedObject( COMMAND_QUEUE_ID ), &command, portMAX_DELAY );
        vTaskDelay(btn_delay);
    }
    return true;
}
