/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2002 by Alan Korr & Nick Robinson
 *
 * All files in this archive are subject to the GNU General Public License.
 * See the file COPYING in the source tree root for full license agreement.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

/* Lingo 0x03: Display Remote Lingo
 *
 * A bit of a hodgepogde of odds and ends.
 *
 * Used to control the equalizer in version 1.00 of the Lingo, but later
 * grew functions to control album art transfer and check the player
 * status.
 *
 * TODO:
 * - Actually support multiple equalizer profiles, currently only the
 *   profile 0 (equalizer disabled) is supported
 */

#include "iap-core.h"
#include "iap-lingo.h"
#include "system.h"
#include "audio.h"
#include "powermgmt.h"
#include "settings.h"
#include "metadata.h"
#include "playback.h"
#if CONFIG_TUNER
#include "ipod_remote_tuner.h"
#endif

/*
 * This macro is meant to be used inside an IAP mode message handler.
 * It is passed the expected minimum length of the message buffer.
 * If the buffer does not have the required lenght an ACK
 * packet with a Bad Parameter error is generated.
 */
#define CHECKLEN(x) do { \
        if (len < (x)) { \
            cmd_ack(cmd, IAP_ACK_BAD_PARAM); \
            return; \
        }} while(0)

/* Check for authenticated state, and return an ACK Not
 * Authenticated on failure.
 */
#define CHECKAUTH do { \
        if (!DEVICE_AUTHENTICATED) { \
            cmd_ack(cmd, IAP_ACK_NO_AUTHEN); \
            return; \
        }} while(0)

static void cmd_ack(const unsigned char cmd, const unsigned char status)
{
    IAP_TX_INIT(0x03, 0x00);
    IAP_TX_PUT(status);
    IAP_TX_PUT(cmd);

    iap_send_tx();
}

#define cmd_ok(cmd) cmd_ack((cmd), IAP_ACK_OK)

void iap_handlepkt_mode3(const unsigned int len, const unsigned char *buf)
{
    unsigned int cmd = buf[1];

    /* We expect at least two bytes in the buffer, one for the
     * state bits.
     */
    CHECKLEN(2);

    /* Lingo 0x03 must have been negotiated */
    if (!DEVICE_LINGO_SUPPORTED(0x03)) {
        cmd_ack(cmd, IAP_ACK_BAD_PARAM);
        return;
    }

    switch (cmd)
    {
        /* ACK (0x00)
         *
         * Sent from the iPod to the device
         */

        /* GetCurrentEQProfileIndex (0x01)
         *
         * Return the index of the current equalizer profile.
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x01
         *
         * Returns:
         * RetCurrentEQProfileIndex
         *
         * Packet format (offset in data[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x02
         * 0x02-0x05: Index as an unsigned 32bit integer
         */
        case 0x01:
        {
            IAP_TX_INIT(0x03, 0x02);
            IAP_TX_PUT_U32(0x00);

            iap_send_tx();
            break;
        }

        /* RetCurrentEQProfileIndex (0x02)
         *
         * Sent from the iPod to the device
         */

        /* SetCurrentEQProfileIndex (0x03)
         *
         * Set the active equalizer profile
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x03
         * 0x02-0x05: Profile index to activate
         * 0x06: Whether to restore the previous profile on detach
         *
         * Returns on success:
         * IAP_ACK_OK
         *
         * Returns on failure:
         * IAP_ACK_CMD_FAILED
         *
         * TODO: Figure out return code for invalid index
         */
        case 0x03:
        {
            uint32_t index;

            CHECKLEN(7);

            index = get_u32(&buf[2]);

            if (index > 0) {
                cmd_ack(cmd, IAP_ACK_BAD_PARAM);
                break;
            }

            /* Currently, we just ignore the command and acknowledge it */
            cmd_ok(cmd);
            break;
        }

        /* GetNumEQProfiles (0x04)
         *
         * Get the number of available equalizer profiles
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x04
         *
         * Returns:
         * RetNumEQProfiles
         *
         * Packet format (offset in data[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x05
         * 0x02-0x05: Number as an unsigned 32bit integer
         */
        case 0x04:
        {
            IAP_TX_INIT(0x03, 0x05);
            /* Return one profile (0, the disabled profile) */
            IAP_TX_PUT_U32(0x01);

            iap_send_tx();
            break;
        }

        /* RetNumEQProfiles (0x05)
         *
         * Sent from the iPod to the device
         */

        /* GetIndexedEQProfileName (0x06)
         *
         * Return the name of the indexed equalizer profile
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x06
         * 0x02-0x05: Profile index to get the name of
         *
         * Returns on success:
         * RetIndexedEQProfileName
         *
         * Packet format (offset in data[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x06
         * 0x02-0xNN: Name as an UTF-8 null terminated string
         *
         * Returns on failure:
         * IAP_ACK_BAD_PARAM
         *
         * TODO: Figure out return code for out of range index
         */
        case 0x06:
        {
            uint32_t index;

            CHECKLEN(6);

            index = get_u32(&buf[2]);

            if (index > 0) {
                cmd_ack(cmd, IAP_ACK_BAD_PARAM);
                break;
            }
            IAP_TX_INIT(0x03, 0x07);
            IAP_TX_PUT_STRING("Default");

            iap_send_tx();
            break;
        }

        /* RetIndexedQUProfileName (0x07)
         *
         * Sent from the iPod to the device
         */

        /* SetRemoteEventNotification (0x08)
         *
         * Set events the device would like to be notified about
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x08
         * 0x02-0x05: Event bitmask
         *
         * Returns:
         * IAP_ACK_OK
         */
        case 0x08:
        {
            struct tm* tm;

            CHECKLEN(6);
            CHECKAUTH;

            /* Save the current state of the various attributes we track */
            device.trackpos_ms = iap_get_trackpos();
            device.track_index = iap_get_trackindex();
            device.chapter_index = 0;
            device.play_status = audio_status();
            /* TODO: Fix this */
            device.mute = false;
            device.volume = global_status.volume;
            device.power_state = charger_input_state;
            device.battery_level = battery_level();
            /* TODO: Fix this */
            device.equalizer_index = 0;
            device.shuffle = global_settings.playlist_shuffle;
            device.repeat = global_settings.repeat_mode;
            tm = get_time();
            memcpy(&(device.datetime), tm, sizeof(struct tm));
            device.alarm_state = 0;
            device.alarm_hour = 0;
            device.alarm_minute = 0;
            /* TODO: Fix this */
            device.backlight = 0;
            device.hold = button_hold();
            device.soundcheck = 0;
            device.audiobook = 0;
            device.trackpos_s = (device.trackpos_ms/1000) & 0xFFFF;

            /* Get the notification bits */
            device.do_notify = false;
            device.changed_notifications = 0;
            device.notifications = get_u32(&buf[0x02]);
            if (device.notifications)
                device.do_notify = true;

            cmd_ok(cmd);
            break;
        }

        /* RemoteEventNotification (0x09)
         *
         * Sent from the iPod to the device
         */

        /* GetRemoteEventStatus (0x0A)
         *
         * Request the events changed since the last call to
         * GetREmoteEventStatus or SetRemoteEventNotification
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x0A
         *
         * This command requires authentication
         *
         * Returns:
         * RetRemoteEventNotification
         *
         * Packet format (offset in data[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x0B
         * 0x02-0x05: Event status bits
         */
        case 0x0A:
        {
            CHECKAUTH;
            IAP_TX_INIT(0x03, 0x0B);
            IAP_TX_PUT_U32(device.changed_notifications);

            iap_send_tx();

            device.changed_notifications = 0;
            break;
        }

        /* RetRemoteEventStatus (0x0B)
         *
         * Sent from the iPod to the device
         */

        /* GetiPodStateInfo (0x0C)
         *
         * Request state information from the iPod
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x0C
         * 0x02: Type information
         *
         * This command requires authentication
         *
         * Returns:
         * RetiPodStateInfo
         *
         * Packet format (offset in data[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x0D
         * 0x02: Type information
         * 0x03-0xNN: State information
         */
        case 0x0C:
        {
            struct mp3entry* id3;
            struct playlist_info* playlist;
            int play_status;
            struct tm* tm;

            CHECKLEN(3);
            CHECKAUTH;

            IAP_TX_INIT(0x03, 0x0D);
            IAP_TX_PUT(buf[0x02]);

            switch (buf[0x02])
            {
                /* 0x00: Track position
                 * Data length: 4
                 */
                case 0x00:
                {
                    id3 = audio_current_track();
                    IAP_TX_PUT_U32(id3->elapsed);

                    iap_send_tx();
                    break;
                }

                /* 0x01: Track index
                 * Data length: 4
                 */
                case 0x01:
                {
                    playlist = playlist_get_current();
                    IAP_TX_PUT_U32(playlist->index - playlist->first_index);

                    iap_send_tx();
                    break;
                }

                /* 0x02: Chapter information
                 * Data length: 8
                 */
                case 0x02:
                {
                    playlist = playlist_get_current();
                    IAP_TX_PUT_U32(playlist->index - playlist->first_index);
                    /* Indicate that track does not have chapters */
                    IAP_TX_PUT_U16(0x0000);
                    IAP_TX_PUT_U16(0xFFFF);

                    iap_send_tx();
                    break;
                }

                /* 0x03: Play status
                 * Data length: 1
                 */
                case 0x03:
                {
                    /* TODO: Handle FF/REW
                     */
                    play_status = audio_status();
                    if (play_status & AUDIO_STATUS_PLAY) {
                        if (play_status & AUDIO_STATUS_PAUSE) {
                            IAP_TX_PUT(0x02);
                        } else {
                            IAP_TX_PUT(0x01);
                        }
                    } else {
                        IAP_TX_PUT(0x00);
                    }

                    iap_send_tx();
                    break;
                }

                /* 0x04: Mute/UI/Volume
                 * Data length: 2
                 */
                case 0x04:
                {
                    if (device.mute == false) {
                        /* Mute status False*/
                        IAP_TX_PUT(0x00);
                        /* Volume */
                        IAP_TX_PUT(0xFF & (int)((global_status.volume + 90) * 2.65625));

                    } else {
                        /* Mute status True*/
                        IAP_TX_PUT(0x01);
                        /* Volume should be 0 if muted */
                        IAP_TX_PUT(0x00);
                    }

                    iap_send_tx();
                    break;
                }

                /* 0x05: Power/Battery
                 * Data length: 2
                 */
                case 0x05:
                {
                    iap_fill_power_state();

                    iap_send_tx();
                    break;
                }

                /* 0x06: Equalizer state
                 * Data length: 4
                 */
                case 0x06:
                {
                    /* Currently only one equalizer setting supported, 0 */
                    IAP_TX_PUT_U32(0x00);

                    iap_send_tx();
                    break;
                }

                /* 0x07: Shuffle
                 * Data length: 1
                 */
                case 0x07:
                {
                    IAP_TX_PUT(global_settings.playlist_shuffle?0x01:0x00);

                    iap_send_tx();
                    break;
                }

                /* 0x08: Repeat
                 * Data length: 1
                 */
                case 0x08:
                {
                    switch (global_settings.repeat_mode)
                    {
                        case REPEAT_OFF:
                        {
                            IAP_TX_PUT(0x00);
                            break;
                        }

                        case REPEAT_ONE:
                        {
                            IAP_TX_PUT(0x01);
                            break;
                        }

                        case REPEAT_ALL:
                        {
                            IAP_TX_PUT(0x02);
                            break;
                        }
                    }

                    iap_send_tx();
                    break;
                }

                /* 0x09: Data/Time
                 * Data length: 6
                 */
                case 0x09:
                {
                    tm = get_time();

                    /* Year */
                    IAP_TX_PUT_U16(tm->tm_year);

                    /* Month */
                    IAP_TX_PUT(tm->tm_mon+1);

                    /* Day */
                    IAP_TX_PUT(tm->tm_mday);

                    /* Hour */
                    IAP_TX_PUT(tm->tm_hour);

                    /* Minute */
                    IAP_TX_PUT(tm->tm_min);

                    iap_send_tx();
                    break;
                }

                /* 0x0A: Alarm
                 * Data length: 3
                 */
                case 0x0A:
                {
                    /* Alarm not supported, always off */
                    IAP_TX_PUT(0x00);
                    IAP_TX_PUT(0x00);
                    IAP_TX_PUT(0x00);

                    iap_send_tx();
                    break;
                }

                /* 0x0B: Backlight
                 * Data length: 1
                 */
                case 0x0B:
                {
                    /* TOOD: Find out how to do this */
                    IAP_TX_PUT(0x00);

                    iap_send_tx();
                    break;
                }

                /* 0x0C: Hold switch
                 * Data length: 1
                 */
                case 0x0C:
                {
                    IAP_TX_PUT(button_hold()?0x01:0x00);

                    iap_send_tx();
                    break;
                }

                /* 0x0D: Sound check
                 * Data length: 1
                 */
                case 0x0D:
                {
                    /* TODO: Find out what the hell this is. Default to off */
                    IAP_TX_PUT(0x00);

                    iap_send_tx();
                    break;
                }

                /* 0x0E: Audiobook
                 * Data length: 1
                 */
                case 0x0E:
                {
                    /* Default to normal */
                    IAP_TX_PUT(0x00);

                    iap_send_tx();
                    break;
                }

                /* 0x0F: Track position in seconds
                 * Data length: 2
                 */
                case 0x0F:
                {
                    unsigned int pos;

                    id3 = audio_current_track();
                    pos = id3->elapsed/1000;

                    IAP_TX_PUT_U16(pos);

                    iap_send_tx();
                    break;
                }

                /* 0x10: Mute/UI/Absolute volume
                 * Data length: 3
                 */
                case 0x10:
                {
                    if (device.mute == false) {
                        /* Mute status False*/
                        IAP_TX_PUT(0x00);
                        /* Volume */
                        IAP_TX_PUT(0xFF & (int)((global_status.volume + 90) * 2.65625));
                        IAP_TX_PUT(0xFF & (int)((global_status.volume + 90) * 2.65625));

                    } else {
                        /* Mute status True*/
                        IAP_TX_PUT(0x01);
                        /* Volume should be 0 if muted */
                        IAP_TX_PUT(0x00);
                        IAP_TX_PUT(0x00);
                    }

                    iap_send_tx();
                    break;
                }
                default:
                {
                    cmd_ack(cmd, IAP_ACK_BAD_PARAM);
                    break;
                }
            }
            break;
        }

        /* RetiPodStateInfo (0x0D)
         *
         * Sent from the iPod to the device
         */

        /* SetiPodStateInfo (0x0E)
         *
         * Set status information to new values
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x0E
         * 0x02: Type of information to change
         * 0x03-0xNN: New information
         *
         * This command requires authentication
         *
         * Returns on success:
         * IAP_ACK_OK
         *
         * Returns on failure:
         * IAP_ACK_CMD_FAILED
         * IAP_ACK_BAD_PARAM
         */
        case 0x0E:
        {
            CHECKLEN(3);
            CHECKAUTH;
            switch (buf[0x02])
            {
                /* Track position (ms)
                 * Data length: 4
                 */
                case 0x00:
                {
                    uint32_t pos;

                    CHECKLEN(7);
                    pos = get_u32(&buf[0x03]);
                    audio_ff_rewind(pos);

                    cmd_ok(cmd);
                    break;
                }

                /* Track index
                 * Data length: 4
                 */
                case 0x01:
                {
                    uint32_t index;

                    CHECKLEN(7);
                    index = get_u32(&buf[0x03]);
                    audio_skip(index-iap_get_trackindex());

                    cmd_ok(cmd);
                    break;
                }

                /* Chapter index
                 * Data length: 2
                 */
                case 0x02:
                {
                    /* This is not supported */
                    cmd_ack(cmd, IAP_ACK_CMD_FAILED);
                    break;
                }

                /* Play status
                 * Data length: 1
                 */
                case 0x03:
                {
                    CHECKLEN(4);
                    switch(buf[0x03])
                    {
                        case 0x00:
                        {
                            audio_stop();
                            cmd_ok(cmd);
                            break;
                        }

                        case 0x01:
                        {
                            audio_resume();
                            cmd_ok(cmd);
                            break;
                        }

                        case 0x02:
                        {
                            audio_pause();
                            cmd_ok(cmd);
                            break;
                        }

                        default:
                        {
                            cmd_ack(cmd, IAP_ACK_CMD_FAILED);
                            break;
                        }
                    }
                    break;
                }

                case 0x04:
                {
                    CHECKLEN(5);
                    if (buf[0x03]==0x00){
                        /* Not Muted */
                        global_status.volume = (int) (buf[0x04]/2.65625)-90;
                        device.mute = false;
                    }
                    else {
                        device.mute = true;
                    }
                    cmd_ok(cmd);
                    break;
                }

                /* Equalizer
                 * Data length: 5
                 */
                case 0x06:
                {
                    uint32_t index;

                    CHECKLEN(8);
                    index = get_u32(&buf[0x03]);
                    if (index == 0) {
                        cmd_ok(cmd);
                    } else {
                        cmd_ack(cmd, IAP_ACK_BAD_PARAM);
                    }
                    break;
                }

                /* Shuffle
                 * Data length: 2
                 */
                case 0x07:
                {
                    CHECKLEN(5);

                    switch(buf[0x03])
                    {
                        case 0x00:
                        {
                            iap_shuffle_state(false);
                            cmd_ok(cmd);
                            break;
                        }
                        case 0x01:
                        case 0x02:
                        {
                            iap_shuffle_state(true);
                            cmd_ok(cmd);
                            break;
                        }

                        default:
                        {
                            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
                            break;
                        }
                    }
                    break;
                }

                /* Repeat
                 * Data length: 2
                 */
                case 0x08:
                {
                    CHECKLEN(5);

                    switch(buf[0x03])
                    {
                        case 0x00:
                        {
                            iap_repeat_state(REPEAT_OFF);
                            cmd_ok(cmd);
                            break;
                        }
                        case 0x01:
                        {
                            iap_repeat_state(REPEAT_ONE);
                            cmd_ok(cmd);
                            break;
                        }
                        case 0x02:
                        {
                            iap_repeat_state(REPEAT_ALL);
                            cmd_ok(cmd);
                            break;
                        }
                        default:
                        {
                            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
                            break;
                        }
                    }
                    break;
                }

                /* Date/Time
                 * Data length: 6
                 */
                case 0x09:
                {
                    CHECKLEN(9);

                    cmd_ack(cmd, IAP_ACK_CMD_FAILED);
                    break;
                }

                /* Alarm
                 * Data length: 4
                 */
                case 0x0A:
                {
                    CHECKLEN(7);

                    cmd_ack(cmd, IAP_ACK_CMD_FAILED);
                    break;
                }

                /* Backlight
                 * Data length: 2
                 */
                case 0x0B:
                {
                    CHECKLEN(5);

                    cmd_ack(cmd, IAP_ACK_CMD_FAILED);
                    break;
                }

                /* Sound check
                 * Data length: 2
                 */
                case 0x0D:
                {
                    CHECKLEN(5);

                    cmd_ack(cmd, IAP_ACK_CMD_FAILED);
                    break;
                }

                /* Audio book speed
                 * Data length: 1
                 */
                case 0x0E:
                {
                    CHECKLEN(4);

                    cmd_ack(cmd, IAP_ACK_CMD_FAILED);
                    break;
                }

                /* Track position (s)
                 * Data length: 2
                 */
                case 0x0F:
                {
                    uint16_t pos;

                    CHECKLEN(5);
                    pos = get_u16(&buf[0x03]);
                    audio_ff_rewind(1000L * pos);

                    cmd_ok(cmd);
                    break;
                }

                /* Volume/Mute/Absolute
                 * Data length: 4
                 * TODO: Fix this
                 */
                case 0x10:
                {
                    CHECKLEN(7);
                    if (buf[0x03]==0x00){
                        /* Not Muted */
                        global_status.volume = (int) (buf[0x04]/2.65625)-90;
                        device.mute = false;
                    }
                    else {
                        device.mute = true;
                    }

                    cmd_ok(cmd);
                    break;
                }

                default:
                {
                    cmd_ack(cmd, IAP_ACK_BAD_PARAM);
                    break;
                }
            }

            break;
        }

        /* GetPlayStatus (0x0F)
         *
         * Request the current play status information
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x0F
         *
         * This command requires authentication
         *
         * Returns:
         * RetPlayStatus
         *
         * Packet format (offset in data[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x10
         * 0x02: Play state
         * 0x03-0x06: Current track index
         * 0x07-0x0A: Current track length (ms)
         * 0x0B-0x0E: Current track position (ms)
         */
        case 0x0F:
        {
            int play_status;
            struct mp3entry* id3;
            struct playlist_info* playlist;

            CHECKAUTH;

            IAP_TX_INIT(0x03, 0x10);

            play_status = audio_status();

            if (play_status & AUDIO_STATUS_PLAY) {
                /* Playing or paused */
                if (play_status & AUDIO_STATUS_PAUSE) {
                    /* Paused */
                    IAP_TX_PUT(0x02);
                } else {
                    /* Playing */
                    IAP_TX_PUT(0x01);
                }
                playlist = playlist_get_current();
                IAP_TX_PUT_U32(playlist->index - playlist->first_index);
                id3 = audio_current_track();
                IAP_TX_PUT_U32(id3->length);
                IAP_TX_PUT_U32(id3->elapsed);
            } else {
                /* Stopped, all values are 0x00 */
                IAP_TX_PUT(0x00);
                IAP_TX_PUT_U32(0x00);
                IAP_TX_PUT_U32(0x00);
                IAP_TX_PUT_U32(0x00);
            }

            iap_send_tx();
            break;
        }

        /* RetPlayStatus (0x10)
         *
         * Sent from the iPod to the device
         */

        /* SetCurrentPlayingTrack (0x11)
         *
         * Set the current playing track
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x11
         * 0x02-0x05: Index of track to play
         *
         * This command requires authentication
         *
         * Returns on success:
         * IAP_ACK_OK
         *
         * Returns on failure:
         * IAP_ACK_BAD_PARAM
         */
        case 0x11:
        {
            uint32_t index;
            uint32_t trackcount;

            CHECKAUTH;
            CHECKLEN(6);

            index = get_u32(&buf[0x02]);
            trackcount = playlist_amount();

            if (index >= trackcount)
            {
                cmd_ack(cmd, IAP_ACK_BAD_PARAM);
                break;
            }
            audio_skip(index-iap_get_trackindex());
            cmd_ok(cmd);

            break;
        }

        /* GetIndexedPlayingTrackInfo (0x12)
         *
         * Request information about a given track
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x12
         * 0x02: Type of information to retrieve
         * 0x03-0x06: Track index
         * 0x07-0x08: Chapter index
         *
         * This command requires authentication.
         *
         * Returns:
         * RetIndexedPlayingTrackInfo
         *
         * Packet format (offset in data[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x13
         * 0x02: Type of information returned
         * 0x03-0xNN: Information
         */
        case 0x12:
        {
            /* NOTE:
             *
             * Retrieving the track information from a track which is not
             * the currently playing track can take a seriously long time,
             * in the order of several seconds.
             *
             * This most certainly violates the IAP spec, but there's no way
             * around this for now.
             */
            uint32_t track_index;
            struct playlist_track_info track;
            struct mp3entry id3;

            CHECKLEN(0x09);
            CHECKAUTH;

            track_index = get_u32(&buf[0x03]);
            if (-1 == playlist_get_track_info(NULL, track_index, &track)) {
                cmd_ack(cmd, IAP_ACK_BAD_PARAM);
                break;
            }

            IAP_TX_INIT(0x03, 0x13);
            IAP_TX_PUT(buf[2]);
            switch (buf[2])
            {
                /* 0x00: Track caps/info
                 * Information length: 10 bytes
                 */
                case 0x00:
                {
                    iap_get_trackinfo(track_index, &id3);
                    /* Track capabilities. None of these are supported, yet */
                    IAP_TX_PUT_U32(0x00);

                    /* Track length in ms */
                    IAP_TX_PUT_U32(id3.length);

                    /* Chapter count, stays at 0 */
                    IAP_TX_PUT_U16(0x00);

                    iap_send_tx();
                    break;
                }

                /* 0x01: Chapter time/name
                 * Information length: 4+variable
                 */
                case 0x01:
                {
                    /* Chapter length, set at 0 (no chapters) */
                    IAP_TX_PUT_U32(0x00);

                    /* Chapter name, empty */
                    IAP_TX_PUT_STRING("");

                    iap_send_tx();
                    break;
                }

                /* 0x02, Artist name
                 * Information length: variable
                 */
                case 0x02:
                {
                    /* Artist name */
                    iap_get_trackinfo(track_index, &id3);
                    IAP_TX_PUT_STRLCPY(id3.artist);

                    iap_send_tx();
                    break;
                }

                /* 0x03, Album name
                 * Information length: variable
                 */
                case 0x03:
                {
                    /* Album name */
                    iap_get_trackinfo(track_index, &id3);
                    IAP_TX_PUT_STRLCPY(id3.album);

                    iap_send_tx();
                    break;
                }

                /* 0x04, Genre name
                 * Information length: variable
                 */
                case 0x04:
                {
                    /* Genre name */
                    iap_get_trackinfo(track_index, &id3);
                    IAP_TX_PUT_STRLCPY(id3.genre_string);

                    iap_send_tx();
                    break;
                }

                /* 0x05, Track title
                 * Information length: variable
                 */
                case 0x05:
                {
                    /* Track title */
                    iap_get_trackinfo(track_index, &id3);
                    IAP_TX_PUT_STRLCPY(id3.title);

                    iap_send_tx();
                    break;
                }

                /* 0x06, Composer name
                 * Information length: variable
                 */
                case 0x06:
                {
                    /* Track Composer */
                    iap_get_trackinfo(track_index, &id3);
                    IAP_TX_PUT_STRLCPY(id3.composer);

                    iap_send_tx();
                    break;
                }

                /* 0x07, Lyrics
                 * Information length: variable
                 */
                case 0x07:
                {
                    /* Packet information bits. All 0 (single packet) */
                    IAP_TX_PUT(0x00);

                    /* Packet index */
                    IAP_TX_PUT_U16(0x00);

                    /* Lyrics */
                    IAP_TX_PUT_STRING("");

                    iap_send_tx();
                    break;
                }

                /* 0x08, Artwork count
                 * Information length: variable
                 */
                case 0x08:
                {
                    /* No artwork, return packet containing just the type byte */
                    iap_send_tx();
                    break;
                }

                default:
                {
                    cmd_ack(cmd, IAP_ACK_BAD_PARAM);
                    break;
                }
            }

            break;
        }

        /* RetIndexedPlayingTrackInfo (0x13)
         *
         * Sent from the iPod to the device
         */

        /* GetNumPlayingTracks (0x14)
         *
         * Request the number of tracks in the current playlist
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x14
         *
         * This command requires authentication.
         *
         * Returns:
         * RetNumPlayingTracks
         *
         * Packet format (offset in data[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x15
         * 0x02-0xNN: Number of tracks
         */
        case 0x14:
        {
            CHECKAUTH;

            IAP_TX_INIT(0x03, 0x15);
            IAP_TX_PUT_U32(playlist_amount());

            iap_send_tx();
        }

        /* RetNumPlayingTracks (0x15)
         *
         * Sent from the iPod to the device
         */

        /* GetArtworkFormats (0x16)
         *
         * Request a list of supported artwork formats
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x16
         *
         * This command requires authentication.
         *
         * Returns:
         * RetArtworkFormats
         *
         * Packet format (offset in data[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x17
         * 0x02-0xNN: list of 7 byte format descriptors
         */
        case 0x16:
        {
            CHECKAUTH;

            /* We return the empty list, meaning no artwork
             * TODO: Fix to return actual artwork formats
             */
            IAP_TX_INIT(0x03, 0x17);

            iap_send_tx();
            break;
        }

        /* RetArtworkFormats (0x17)
         *
         * Sent from the iPod to the device
         */

        /* GetTrackArtworkData (0x18)
         *
         * Request artwork for the given track
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x18
         * 0x02-0x05: Track index
         * 0x06-0x07: Format ID
         * 0x08-0x0B: Track offset in ms
         *
         * This command requires authentication.
         *
         * Returns:
         * RetTrackArtworkData
         *
         * Packet format (offset in data[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x19
         * 0x02-0x03: Descriptor index
         * 0x04: Pixel format code
         * 0x05-0x06: Image width in pixels
         * 0x07-0x08: Image height in pixels
         * 0x09-0x0A: Inset rectangle, top left x
         * 0x0B-0x0C: Inset rectangle, top left y
         * 0x0D-0x0E: Inset rectangle, bottom right x
         * 0x0F-0x10: Inset rectangle, bottom right y
         * 0x11-0x14: Row size in bytes
         * 0x15-0xNN: Image data
         *
         * If the image data does not fit in a single packet, subsequent
         * packets omit bytes 0x04-0x14.
         */
        case 0x18:
        {
            CHECKAUTH;
            CHECKLEN(0x0C);

            /* No artwork support currently */
            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
            break;
        }

        /* RetTrackArtworkFormat (0x19)
         *
         * Sent from the iPod to the device
         */

        /* GetPowerBatteryState (0x1A)
         *
         * Request the current power state
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x1A
         *
         * This command requires authentication.
         *
         * Returns:
         * RetPowerBatteryState
         *
         * Packet format (offset in data[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x1B
         * 0x02: Power state
         * 0x03: Battery state
         */
        case 0x1A:
        {
            IAP_TX_INIT(0x03, 0x1B);

            iap_fill_power_state();
            iap_send_tx();
            break;
        }

        /* RetPowerBatteryState (0x1B)
         *
         * Sent from the iPod to the device
         */

        /* GetSoundCheckState (0x1C)
         *
         * Request the current sound check state
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x1C
         *
         * This command requires authentication.
         *
         * Returns:
         * RetSoundCheckState
         *
         * Packet format (offset in data[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x1D
         * 0x02: Sound check state
         */
        case 0x1C:
        {
            CHECKAUTH;

            IAP_TX_INIT(0x03, 0x1D);
            IAP_TX_PUT(0x00);       /* Always off */

            iap_send_tx();
            break;
        }

        /* RetSoundCheckState (0x1D)
         *
         * Sent from the iPod to the device
         */

        /* SetSoundCheckState (0x1E)
         *
         * Set the sound check state
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x1E
         * 0x02: Sound check state
         * 0x03: Restore on exit
         *
         * This command requires authentication.
         *
         * Returns on success
         * IAP_ACK_OK
         *
         * Returns on failure
         * IAP_ACK_CMD_FAILED
         */
        case 0x1E:
        {
            CHECKAUTH;
            CHECKLEN(4);

            /* Sound check is not supported right now
             * TODO: Fix
             */

            cmd_ack(cmd, IAP_ACK_CMD_FAILED);
            break;
        }

        /* GetTrackArtworkTimes (0x1F)
         *
         * Request a list of timestamps at which artwork exists in a track
         *
         * Packet format (offset in buf[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x1F
         * 0x02-0x05: Track index
         * 0x06-0x07: Format ID
         * 0x08-0x09: Artwork Index
         * 0x0A-0x0B: Artwork count
         *
         * This command requires authentication.
         *
         * Returns:
         * RetTrackArtworkTimes
         *
         * Packet format (offset in data[]: Description)
         * 0x00: Lingo ID: Display Remote Lingo, always 0x03
         * 0x01: Command, always 0x20
         * 0x02-0x05: Offset in ms
         *
         * Bytes 0x02-0x05 can be repeated multiple times
         */
        case 0x1F:
        {
            uint32_t index;
            uint32_t trackcount;

            CHECKAUTH;
            CHECKLEN(0x0C);

            /* Artwork is currently unsuported, just check for a valid
             * track index
             */
            index = get_u32(&buf[0x02]);
            trackcount = playlist_amount();

            if (index >= trackcount)
            {
                cmd_ack(cmd, IAP_ACK_BAD_PARAM);
                break;
            }

            /* Send an empty list */
            IAP_TX_INIT(0x03, 0x20);

            iap_send_tx();
            break;
        }

        /* The default response is IAP_ACK_BAD_PARAM */
        default:
        {
#ifdef LOGF_ENABLE
            logf("iap: Unsupported Mode03 Command");
#endif
            cmd_ack(cmd, IAP_ACK_BAD_PARAM);
            break;
        }
    }
}
