/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 * 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"

static bool powered = false;

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 bool rds_event = false;

static char rds_radioname[9];
static char rds_radioinfo[65];

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

void rmt_tuner_freq(const unsigned char *serbuf)
{
    unsigned int khz = (serbuf[3] << 24) | (serbuf[4] << 16) |
                       (serbuf[5] << 8) | serbuf[6];
    tuner_frequency = khz *1000 ;
    radio_tuned = true;
    rmt_tuner_signal_power(serbuf[7]);
}

static void rmt_tuner_set_freq(int curr_freq)
{
    if (curr_freq != tuner_frequency)
    {
        radio_tuned = false;
        tuner_signal_power = 0;
        /* clear rds name and info */
        memset(rds_radioname,' ',sizeof(rds_radioname));
        memset(rds_radioinfo,' ',sizeof(rds_radioinfo));
        /* 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)
    {
        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));
        /* boost gain */
        const unsigned char data1[] = {0x07, 0x24, 0x06 };
        iap_send_pkt(data1, sizeof(data1));
        /* set volume */
        unsigned char data2[] = {0x03, 0x09, 0x04, 0x00, 0x00 };
        data2[4] = (char)((global_settings.volume+58) * 4);
        iap_send_pkt(data2, sizeof(data2));
        /* set rds on */
        const unsigned char data3[] = {0x07, 0x20, 0x40, 0x00, 0x00, 0x10 };
        iap_send_pkt(data3, sizeof(data3));
    }
    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));
    }
}

static 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));
}

#if 0   /* function is not used */
static void rmt_tuner_mute(int value)
{
    /* mute flag off (play) */
    unsigned char data[] = {0x03, 0x09, 0x03, 0x01};
    if (value)
    {
        /* mute flag on (pause) */
        data[3] = 0x02;
    }
    iap_send_pkt(data, sizeof(data));
}
#endif

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};
        if (rd->freq_min == 76000000)
        {
            data[2] = 0x02; /* japan band */
        }
        else
        {
            data[2] = 0x01; /* us eur 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;
            break;
        }
        default:
        {
            tuner_param |= 0x00;
            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
    {
        iap_handlepkt();
        sleep(HZ/50);
        timeout++;
    }
    while((ipod_rmt_tuner_get(RADIO_TUNED) == 0) && (timeout < TIMEOUT_VALUE));
    
    return (timeout >= TIMEOUT_VALUE);
}

void rmt_tuner_rds_data(const unsigned char *serbuf)
{
    if (serbuf[3] == 0x1E)
    {
        strlcpy(rds_radioname,serbuf+5,8);
    }
    else if(serbuf[3] == 0x04)
    {
        strlcpy(rds_radioinfo,serbuf+5,(serbuf[0]-4));
    }
    rds_event = true;
}
    
/* 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:
        {
            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;
            
        case RADIO_EVENT:
            if (rds_event)
            {
                val = 1;
                rds_event = false;
            }
            break;
    }
    return val;
}

char* ipod_get_rds_info(int setting)
{
    char *text = NULL;
    
    switch(setting)
    {
        case RADIO_RDS_NAME:
            text = rds_radioname;
            break;

        case RADIO_RDS_TEXT:
            text = rds_radioinfo;
            break;
    }
    return text;
}

bool tuner_power(bool status)
{
    bool oldstatus = powered;
    powered = status;
    return oldstatus;
}
