/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * tuner for the ipod fm remote and other ipod remote tuners
 *
 * Copyright (C) 2009 Laurent Gautier
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/
#include "config.h"
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include "kernel.h"
#include "iap.h"
#include "tuner.h" /* tuner abstraction interface */
#include "adc.h"
#include "settings.h"
#include "power.h"
#include "rds.h"

static unsigned char tuner_param = 0x00, old_tuner_param = 0xFF;
/* temp var for tests to avoid looping execution in submenus settings*/
static int mono_mode = -1, old_region = -1;

int radio_present = 0;

static int tuner_frequency = 0;
static int tuner_signal_power = 0;
static bool radio_tuned = false;

static void rmt_tuner_signal_power(unsigned char value)
{
    tuner_signal_power = (int)(value);
}

void rmt_tuner_freq(unsigned int len, const unsigned char *buf)
{
    /* length currently unused */
    (void)len;

    unsigned int khz = (buf[2] << 24) | (buf[3] << 16) |
                       (buf[4] << 8) | buf[5];
    tuner_frequency = khz *1000 ;
    radio_tuned = true;
    rmt_tuner_signal_power(buf[6]);
}

static void rmt_tuner_set_freq(int curr_freq)
{
    if (curr_freq != tuner_frequency)
    {
        radio_tuned = false;
        tuner_signal_power = 0;
        /* clear rds data */
        rds_reset();
        /* ex: 00 01 63 14 = 90.9MHz */
        unsigned char data[] = {0x07, 0x0B, 0x00, 0x01, 0x63, 0x14};

        if (curr_freq != 0)
        {
            unsigned int khz = curr_freq / 1000;
            data[2] = (khz >> 24) & 0xFF;
            data[3] = (khz >> 16) & 0xFF;
            data[4] = (khz >>  8) & 0xFF;
            data[5] = (khz >>  0) & 0xFF;
            iap_send_pkt(data, sizeof(data));
        }
    }
}

static void rmt_tuner_sleep(int state)
{
    if (state == 0)
    {
        rds_init();
        tuner_param = 0x00;
        old_tuner_param = 0xFF;
        mono_mode = -1;
        old_region = -1;
        tuner_frequency = 0;
        radio_tuned = false;

        /* tuner HW on */
        const unsigned char data[] = {0x07, 0x05, 0x01};
        iap_send_pkt(data, sizeof(data));
        /* set rds on */
        const unsigned char data3[] = {0x07, 0x20, 0x40, 0x00, 0x00, 0x10 };
        iap_send_pkt(data3, sizeof(data3));
        /* boost gain */
        const unsigned char data1[] = {0x07, 0x24, 0x06 };
        iap_send_pkt(data1, sizeof(data1));
        /* tuner mode */
        const unsigned char data4[] = {0x07, 0x0E, 0x00 };
        iap_send_pkt(data4, sizeof(data3));
        /* set volume */
        unsigned char data2[] = {0x03, 0x09, 0x04, 0x00, 0x00 };
        data2[4] = (char)((global_status.volume+58) * 4);
        iap_send_pkt(data2, sizeof(data2));
    }
    else
    {
        /* unbooste gain */
        const unsigned char data[] = {0x07, 0x24, 0x00};
        iap_send_pkt(data, sizeof(data));
        /* set rds off */
        const unsigned char data1[] = {0x07, 0x20, 0x00, 0x00, 0x00, 0x00 };
        iap_send_pkt(data1, sizeof(data1));
        /* stop tuner HW */
        const unsigned char data2[] = {0x07, 0x05, 0x00};
        iap_send_pkt(data2, sizeof(data2));
    }
}

void rmt_tuner_scan(int param)
{
    const unsigned char data[] = {0x07, 0x11, 0x08};  /* RSSI level */
    unsigned char updown = 0x00;
    radio_tuned = false;
    iap_send_pkt(data, sizeof(data));

    if (param == 1)
    {
        updown = 0x07;  /* scan up */
    }
    else if (param == -1)
    {
        updown = 0x08;  /* scan down */
    }
    else if (param == 10)
    {
        updown = 0x01;  /* scan up starting from beginning of the band */
    }
    unsigned char data1[] = {0x07, 0x12, updown};
    iap_send_pkt(data1, sizeof(data1));
}

static void rmt_tuner_mute(int value)
{
    /* mute flag off (play) */
    /* The Apple Tuner does NOT appear to support muting. The Apple
     * firmware turns the power off when pressing pause on the iPod
     * or on the Tuner Remote.
     */
    if (value)
    {
        /* mute flag on (pause) */
        unsigned char data[] = {0x03, 0x09, 0x03, 0x02};
        iap_send_pkt(data, sizeof(data));
        rmt_tuner_sleep(1);
    }
    else
    {
        unsigned char data[] = {0x03, 0x09, 0x03, 0x01};
        iap_send_pkt(data, sizeof(data));
        rmt_tuner_sleep(0);
    }
}

static void rmt_tuner_region(int region)
{
    if (region != old_region)
    {
        const struct fm_region_data *rd = &fm_region_data[region];
        unsigned char data[] = {0x07, 0x08, 0x00};
                /* Apple MFi Accessory Firmware Spec R46 now lists
                 * the following bands
                 * ID00 AM 520-1710Khz      Not Supported
                 * ID02 Japan 76-90Mkz 100Khz 50/75uS
                 * ID01 87.5-108Mhz US 200Khz 75uS, EU 100Kz 50uS
                 * ID03 76-108Mhz Wideband. Not Supported
         */
        if (rd->freq_min == 76000000)
        {
            data[2] = 0x02; /* japan band */
        }
        else
        {
            data[2] = 0x01; /* us/europe band */
        }
        iap_send_pkt(data, sizeof(data));
        sleep(HZ/100);
        old_region = region;
    }
}

/* set stereo/mono, deemphasis, delta freq... */
static void rmt_tuner_set_param(unsigned char tuner_param)
{
    if(tuner_param != old_tuner_param)
    {
        unsigned char data[] = {0x07, 0x0E, 0x00};

        data[2] = tuner_param;
        iap_send_pkt(data, sizeof(data));
        old_tuner_param = tuner_param;
    }
}

static void set_deltafreq(int delta)
{
    tuner_param &= 0xFC;
    switch (delta)
    {
        case 1:
        {
            /* 100KHz */
            tuner_param |= 0x01;
            break;
        }
        case 2:
        {
            /* 50KHz */
            tuner_param |= 0x02;
        break;
        }

        default:
        {
            /* 200KHz */
            tuner_param |= 0x00;
            break;
        }
    }
}

static void set_deemphasis(int deemphasis)
{
    tuner_param &= 0xBF;
    switch (deemphasis)
    {
        case 1:
        {
            tuner_param |= 0x40;
            /* 50uS */
            break;
        }
        default:
        {
            tuner_param |= 0x00;
            /* 75uS */
            break;
        }
    }
}

static void set_mono(int value)
{
    tuner_param &= 0xEF;

    if (value != mono_mode)
    {
        tuner_param &= 0xEF;
        if (value == 1)
            tuner_param |= 0x10;
        rmt_tuner_set_param(tuner_param);
        sleep(HZ/100);
        mono_mode = value;
    }
}

static bool reply_timeout(void)
{
    int timeout = 0;

    sleep(HZ/50);
    do
    {
        sleep(HZ/50);
        timeout++;
    }
    while((ipod_rmt_tuner_get(RADIO_TUNED) == 0) && (timeout < TIMEOUT_VALUE));

    return (timeout >= TIMEOUT_VALUE);
}

void rmt_tuner_rds_data(unsigned int len, const unsigned char *buf)
{
    if (buf[2] == 0x1E)
    {
        rds_push_info(RDS_INFO_PS, (uintptr_t)(buf+4), 8);
    }
    else if(buf[2] == 0x04)
    {
        rds_push_info(RDS_INFO_RT, (uintptr_t)(buf+4), len-4);
    }
}

/* tuner abstraction layer: set something to the tuner */
int ipod_rmt_tuner_set(int setting, int value)
{
    switch(setting)
    {
        case RADIO_SLEEP:
        {
            rmt_tuner_sleep(value);
            sleep(HZ/10);
            if(value)
            {
                tuner_frequency = 0;
            }
            break;
        }

        case RADIO_FREQUENCY:
        {
            rmt_tuner_set_freq(value);
            if (reply_timeout())
                return 0;
            break;
        }

        case RADIO_SCAN_FREQUENCY:
        {
            const struct fm_region_data * const fmr =
            &fm_region_data[global_settings.fm_region];

            /* case: scan for presets, back to beginning of the band */
            if (radio_tuned && (value == fmr->freq_min))
            {
                tuner_set(RADIO_FREQUENCY,value);
            }

            /* scan through frequencies */
            if (radio_tuned)
            {
                if ((tuner_frequency <= fmr->freq_min)
                    && (tuner_frequency >= fmr->freq_max))
                {
                    tuner_set(RADIO_FREQUENCY,value);
                }
                /* scan down */
                if(value < tuner_frequency)
                    rmt_tuner_scan(-1);
                /* scan up */
                else
                    rmt_tuner_scan(1);

                sleep(HZ/10);
                if (reply_timeout())
                {
                    tuner_set(RADIO_FREQUENCY,value);
                    rmt_tuner_scan(1);
                    if (reply_timeout() == true)
                        return 0;
                }
                radio_tuned = false;
            }

            if (tuner_frequency == value)
            {
                radio_tuned = true;
                return 1;
            }
            else
            {
                radio_tuned = false;
                return 0;
            }
        }

        case RADIO_MUTE:
        {
            /* mute flag sent to accessory */
            rmt_tuner_mute(value);
            break;
        }

        case RADIO_REGION:
        {
            /* The latest MFi Accessory Firmware Document I have lists the
             * following regions
             * US 87.5-108Mhz 200Khz 75uS
             * US/EU 87.5-108Mhz 100Khz 75/50uS
             * JP 76.0-90Mhz 100Mhz 50/75uS
             *
             * with the following bands
             * 0x00 AM WordlWide 520-1710Khz
             * 0x01 FM EU 87.5-108.0Mhz
             * 0x02 FM JP 76.0-90.0Mhz
             * 0x03 FM Wide 76.0-108.0Mhz
             *
             *
             * A 7G Classic with the latest Apple Firmware returns the following
                         * regions with the settings listed
             * Americas   87.5-108 200Khz 75uS
             * Asia       87.5-108 100Khz 75uS
             * Australia  87.5-108 200Khz 75uS
             * Europe     87.5-108 100Khz 75uS
             * Japan      76.0-90. 100Kz  75uS
             */
            const struct fm_region_data *rd = &fm_region_data[value];
            int band = (rd->freq_min == 76000000) ? 2 : 0;
            int spacing = (100000 / rd->freq_step);
            int deemphasis = (rd->deemphasis == 50) ? 1 : 0;

            rmt_tuner_region(band);
            set_deltafreq(spacing);
            set_deemphasis(deemphasis);
            rmt_tuner_set_param(tuner_param);
            break;
        }

        case RADIO_FORCE_MONO:
        {
            set_mono(value);
            break;
        }

        default:
            return -1;
    }
    return 1;
}

/* tuner abstraction layer: read something from the tuner */
int ipod_rmt_tuner_get(int setting)
{
    int val = -1; /* default for unsupported query */

    switch(setting)
    {
        case RADIO_PRESENT:
            val = radio_present;
            if (val)
            {
                /* if accessory disconnected */
                if(adc_read(ADC_ACCESSORY) >= 10)
                {
                    radio_present = 0;
                    val = 0;
                }
            }
            break;

        /* radio tuned: yes no */
        case RADIO_TUNED:
            val = 0;
            if (radio_tuned)
                val = 1;
            break;

        /* radio is always stereo */
        /* we can't know when it's in mono mode, depending of signal quality */
        /* except if it is forced in mono mode */
        case RADIO_STEREO:
            val = true;
            break;
    }
    return val;
}
