/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2005 by Linus Nielsen Feltzing
 *
 * 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.
 *
 ****************************************************************************/

#include "config.h"
#include "debug.h"
#include "panic.h"
#include "thread.h"

#include <kernel.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <string.h>

#include "cpu.h"
#include "i2c.h"
#include "uda1380.h"
#include "system.h"
#include "usb.h"

#include "buffer.h"
#include "audio.h"
#include "button.h"
#include "file.h"
#include "sprintf.h"
#include "logf.h"
#include "button.h"
#include "lcd.h"
#include "lcd-remote.h"
#include "pcm_playback.h"
#include "pcm_record.h"


/***************************************************************************/

static volatile bool is_recording;              /* We are recording */
static volatile bool is_stopping;               /* Are we going to stop */
static volatile bool is_paused;                 /* We have paused   */

static volatile int num_rec_bytes;
static volatile int int_count;                  /* Number of DMA completed interrupts */
static volatile int error_count;                /* Number of DMA errors */

static unsigned long record_start_time;         /* Value of current_tick when recording was started */
static unsigned long pause_start_time;          /* Value of current_tick when pause was started */

static int rec_gain, rec_volume;
static bool show_waveform;                      
static int init_done = 0;
static int wav_file;
static char recording_filename[MAX_PATH];

/***************************************************************************/

/*
   Some estimates:
    44100 HZ * 4 = 176400 bytes/s
    Refresh LCD 10 HZ = 176400 / 10 = 17640 bytes ~=~ 1024*16 bytes
    
    If NUM_BUFFERS is 80 we can hold ~8 sec of data in memory
    ALL_BUFFER_SIZE will be 1024*16 * 80 = 1310720 bytes
*/

#define NUM_BUFFERS       80        
#define EACH_BUFFER_SIZE  (1024*16)     /* Multiple of 4. Use small value to get responsive waveform */
#define ALL_BUFFERS_SIZE  (NUM_BUFFERS * EACH_BUFFER_SIZE)

#define WRITE_THRESHOLD   40            /* Minimum number of buffers before write to file */

static unsigned char *rec_buffers[NUM_BUFFERS];

/* 
 Overrun occures when DMA needs to write a new buffer and write_index == read_index 
 Solution to this is to optimize pcmrec_callback, use cpu_boost somewhere or increase 
 the total buffer size (or WRITE_THRESHOLD)
*/

static int write_index;       /* Which buffer the DMA is currently recording */
static int read_index;        /* The oldest buffer that the pcmrec_callback has not read */

/***************************************************************************/

static struct event_queue  pcmrec_queue;
static long                pcmrec_stack[(DEFAULT_STACK_SIZE + 0x1000)/sizeof(long)];
static const char          pcmrec_thread_name[] = "pcmrec";

static void pcmrec_thread(void);

/* Event IDs */
#define PCMREC_OPEN         1     /* Enable recording */
#define PCMREC_CLOSE        2     /* Disable recording */
#define PCMREC_START        3     /* Start a new recording */
#define PCMREC_STOP         4     /* Stop the current recording */
#define PCMREC_PAUSE        10
#define PCMREC_RESUME       11
#define PCMREC_NEW_FILE     12
#define PCMREC_SET_GAIN     13
#define PCMREC_GOT_DATA     20    /* DMA1 notifies when data has arrived */


/*******************************************************************/
/* Functions that are not executing in the pcmrec_thread first     */
/*******************************************************************/

void pcm_init_recording(void)
{
    int_count = 0;
    error_count = 0;

    show_waveform = 0;
    is_recording = 0;
    is_stopping = 0;
    num_rec_bytes = 0;
    wav_file = -1;  
    read_index = 0;
    write_index = 0;
    
    queue_init(&pcmrec_queue);
    create_thread(pcmrec_thread, pcmrec_stack, sizeof(pcmrec_stack), pcmrec_thread_name);
}

void pcm_open_recording(void)
{
    init_done = 0;
    
    logf("pcm_open_rec");
    
    queue_post(&pcmrec_queue, PCMREC_OPEN, 0);
    
    while (init_done)
    {
        sleep(HZ >> 8);
    }
        
    logf("pcm_open_rec done");
}

void pcm_close_recording(void)
{
    /* todo: synchronize completion with pcmrec thread */
    queue_post(&pcmrec_queue, PCMREC_CLOSE, 0);
}



unsigned long pcm_status(void)
{
    unsigned long ret = 0;
    
    if (is_recording)
        ret |= AUDIO_STATUS_RECORD;
        
    return ret;
}



void pcm_new_file(const char *filename)
{
    /* todo */
    filename = filename;

}

unsigned long pcm_recorded_time(void)
{
    if (is_recording)
    {
        if(is_paused)
            return pause_start_time - record_start_time;
        else
            return current_tick - record_start_time;
    }

    return 0;
}

unsigned long pcm_num_recorded_bytes(void)
{
   
    if (is_recording)
    {
        return num_rec_bytes;
    }
    else
        return 0;
}

void pcm_pause_recording(void)
{
    /* todo */
}

void pcm_resume_recording(void)
{
    /* todo */
}


/**
 * Sets the audio source 
 * 
 * Side effect: This functions starts feeding the CPU with audio data over the I2S bus
 *
 * @param source 0=line-in, 1=mic 
 */
void pcm_set_recording_options(int source, bool enable_waveform)
{
    uda1380_enable_recording(source);
    
    show_waveform = enable_waveform;
    
}


/**
 *
 * @param gain   line-in and microphone gain (0-15)
 * @param volume ADC volume (0-255)
 */
void pcm_set_recording_gain(int gain, int volume)
{
    rec_gain = gain;
    rec_volume = volume;
    
    queue_post(&pcmrec_queue, PCMREC_SET_GAIN, 0);
    
}
         
/**
 * Start recording
 * 
 * Use pcm_set_recording_options before calling record
 */                               
void pcm_record(const char *filename)
{
    strncpy(recording_filename, filename, MAX_PATH - 1);
    recording_filename[MAX_PATH - 1] = 0;
    
    queue_post(&pcmrec_queue, PCMREC_START, 0);
}

/**
 * 
 */
void pcm_stop_recording(void)
{
    if (is_recording)
    is_stopping = 1;
    
    queue_post(&pcmrec_queue, PCMREC_STOP, 0);

    logf("pcm_stop_recording");
    
    while (is_stopping)
    {
        sleep(HZ >> 4);
    }
    
    logf("pcm_stop_recording done");
    
    
}


/***************************************************************************/
/* Functions that executes in the context of pcmrec_thread                 */
/***************************************************************************/


/**
 * Process the buffers using read_index and write_index.
 *
 * DMA1 handler posts to pcmrec_queue so that pcmrec_thread calls this 
 * function. Also pcmrec_stop will call this function when the recording 
 * is stopping, and that call will have flush = true.
 *
 */

void pcmrec_callback(bool flush) __attribute__ ((section (".icode")));
void pcmrec_callback(bool flush)
{
    int num_ready;

    num_ready = write_index - read_index;
    if (num_ready < 0)
        num_ready += NUM_BUFFERS;

    /* we can consume up to num_ready buffers */
        
#ifdef HAVE_REMOTE_LCD    
    /* Draw waveform on remote LCD */
    if (show_waveform && num_ready>0)          
    {
        short *buf; 
        long x,y,offset;
        int show_index;

        /* Just display the last buffer (most recent one) */  
        show_index = read_index + num_ready - 1;          
        buf = (short*)rec_buffers[show_index]; 
    
        lcd_remote_clear_display();    
        
        offset = 0;
        for (x=0; x<LCD_REMOTE_WIDTH-1; x++)
        {
            y = buf[offset] * (LCD_REMOTE_HEIGHT / 2)  *5;       /* The 5 is just 'zooming' */
            y = y >> 15;                /* Divide with SHRT_MAX */
            y += LCD_REMOTE_HEIGHT/2;
            
            if (y < 2) y=2;
            if (y >= LCD_REMOTE_HEIGHT-2) y = LCD_REMOTE_HEIGHT-2;
    
            lcd_remote_drawpixel(x,y);
            
            offset += (EACH_BUFFER_SIZE/2) / LCD_REMOTE_WIDTH;  
        }
      
        lcd_remote_update();   
    }
       
#endif      

    /* Note: This might be a good place to call the 'codec' later */

    /* Check that we have the minimum amount of data to save or */
    /* that if it's closing time which mean we have to save..   */
    if (wav_file != -1)
    {
        if (num_ready >= WRITE_THRESHOLD || flush)
        {
            unsigned long *ptr = (unsigned long*)rec_buffers[read_index];
            int i;
            
            for (i=0; i<EACH_BUFFER_SIZE * num_ready / 4; i++)
            {
                *ptr = SWAB32(*ptr);
                ptr++;
            }
            
            write(wav_file, rec_buffers[read_index], EACH_BUFFER_SIZE * num_ready);
        
            read_index+=num_ready;
            if (read_index >= NUM_BUFFERS)
                read_index -= NUM_BUFFERS;    
        }
        
    } else
    {
        /* In this case we must consume the buffers otherwise we will */
        /* get 'dma1 overrun' pretty fast */
        
        read_index+=num_ready;
        if (read_index >= NUM_BUFFERS)
            read_index -= NUM_BUFFERS;
    }
}

    


void pcmrec_dma_start(void)
{
    DAR1 = (unsigned long)rec_buffers[write_index++];  /* Destination address */
    SAR1 = (unsigned long)&PDIR2;                      /* Source address */
    BCR1 = EACH_BUFFER_SIZE;                           /* Bytes to transfer */
                        
    /* Start the DMA transfer.. */
    DCR1 = DMA_INT | DMA_EEXT | DMA_CS | DMA_DINC | DMA_START;        
    
    logf("dma1 started");
}


/* DMA1 Interrupt is called when the DMA has finished transfering a chunk */
void DMA1(void) __attribute__ ((interrupt_handler, section(".icode")));
void DMA1(void)
{
    int res = DSR1;

    DSR1 = 1;    /* Clear interrupt */
    
    int_count++;

    if (res & 0x70)
    {
        DCR1 = 0;   /* Stop DMA transfer */
        error_count++;
        is_recording = 0;
        
        logf("dma1 err 0x%x", res);
        
    } else 
    {
        num_rec_bytes += EACH_BUFFER_SIZE;
        
        write_index++;
        if (write_index >= NUM_BUFFERS)
            write_index = 0;
        
        if (is_stopping || !is_recording)
        {
            DCR1 = 0;   /* Stop DMA transfer */
            is_recording = 0;
            
            logf("dma1 stopping");
            
        } else if (write_index == read_index)
        {
            DCR1 = 0;   /* Stop DMA transfer */
            is_recording = 0;
            
            logf("dma1 overrun");
            
        } else
        {
            DAR1 = (unsigned long)rec_buffers[write_index];  /* Destination address */
            BCR1 = EACH_BUFFER_SIZE;

            queue_post(&pcmrec_queue, PCMREC_GOT_DATA, NULL);
            
        }
    }
    
    IPR |= (1<<15); /* Clear pending interrupt request */
}

static int start_wave(void)
{
    unsigned char header[44] = 
    {
        'R','I','F','F',0,0,0,0,'W','A','V','E','f','m','t',' ',
        0x10,0,0,0,1,0,2,0,0x44,0xac,0,0,0x10,0xb1,2,0,
        4,0,0x10,0,'d','a','t','a',0,0,0,0
    };
    
    wav_file = open(recording_filename, O_RDWR|O_CREAT|O_TRUNC);
    if (wav_file < 0)
    {
        wav_file = -1;
        logf("create failed: %d", wav_file);
        return -1;
    }
    
    if (sizeof(header) != write(wav_file, header, sizeof(header)))
    {
        close(wav_file);
        wav_file = -1;
        logf("write failed");
        return -2;
    }
    
    return 0;
}

/* Update header and set correct length values */
static void close_wave(void)
{
    long l;
    
    l = SWAB32(num_rec_bytes + 36);
    lseek(wav_file, 4, SEEK_SET);
    write(wav_file, &l, 4);
        
    l = SWAB32(num_rec_bytes);
    lseek(wav_file, 40, SEEK_SET);
    write(wav_file, &l, 4);
    
    close(wav_file);
    wav_file = -1;
}

static void pcmrec_start(void)
{
    logf("pcmrec_start");
    
    if (is_recording) 
        return; 
        
    if (wav_file != -1)
        close(wav_file);
        
    logf("rec: %s", recording_filename);
    
    start_wave(); /* todo: send signal to pcm_record if we have failed */

    num_rec_bytes = 0;
    
    /* Store the current time */
    record_start_time = current_tick;

    write_index = 0;
    read_index = 0;

    is_stopping = 0;
    is_paused = 0;
    is_recording = 1;

    pcmrec_dma_start();
    
}

static void pcmrec_stop(void)
{
    /* wait for recording to finish */
    
    /* todo: Abort current DMA transfer using DCR1.. */
    
    logf("pcmrec_stop");
    
    while (is_recording)
    {
        sleep(HZ >> 4);
    }
    
    logf("pcmrec_stop done");

    /* Write unfinished buffers to file */
    pcmrec_callback(true);        

    close_wave();
    
    is_stopping = 0;   
}

static void pcmrec_open(void)
{
    unsigned long buffer_start;
    int i;
    
    show_waveform = 0;
    is_recording = 0;
    is_stopping = 0;
    num_rec_bytes = 0;
    wav_file = -1;  
    read_index = 0;
    write_index = 0;

    buffer_start = (unsigned long)(&audiobuf[(audiobufend - audiobuf) - (ALL_BUFFERS_SIZE + 16)]);
    buffer_start &= ~3;
    
    for (i=0; i<NUM_BUFFERS; i++)
    {
        rec_buffers[i] = (unsigned char*)(buffer_start + EACH_BUFFER_SIZE * i);
    }

    IIS1CONFIG = 0x800;             /* Stop any playback                              */
    AUDIOGLOB |= 0x180;             /* IIS1 fifo auto sync = on, PDIR2 auto sync = on */
    DATAINCONTROL = 0xc000;         /* Generate Interrupt when 6 samples in fifo      */
    DATAINCONTROL |= 0x20;          /* PDIR2 source = IIS1recv                        */

    DIVR1 = 55;                     /* DMA1 is mapped into vector 55 in system.c      */
    DMACONFIG = 1;                  /* DMA0Req = PDOR3, DMA1Req = PDIR2               */
    DMAROUTE = (DMAROUTE & 0xffff00ff) | DMA1_REQ_AUDIO_2;
    ICR4 = (ICR4 & 0xffffff00) | 0x0000001c;  /* Enable interrupt at level 7, priority 0 */
    IMR &= ~(1<<15);                /* bit 15 is DMA1 */

    init_done = 1;    
}

static void pcmrec_close(void)
{
    uda1380_disable_recording();

    DMAROUTE = (DMAROUTE & 0xffff00ff);
    ICR4 = (ICR4 & 0xffffff00);     /* Disable interrupt */
    IMR |= (1<<15);                 /* bit 15 is DMA1 */        

}

static void pcmrec_thread(void)
{
    struct event ev;
    
    logf("thread pcmrec start");
    
    while (1)
    {
        queue_wait(&pcmrec_queue, &ev);
        
        switch (ev.id)
        {
            case PCMREC_OPEN:
                pcmrec_open();
                break;
                
            case PCMREC_CLOSE:
                pcmrec_close();
                break;    
            
            case PCMREC_START:
                pcmrec_start();
                break;
            
            case PCMREC_STOP:
                pcmrec_stop();
                break;
            
            case PCMREC_PAUSE:
                /* todo */
                break;
            
            case PCMREC_RESUME:
                /* todo */
                break;
            
            case PCMREC_NEW_FILE:
                /* todo */
                break;
            
            case PCMREC_SET_GAIN:
                uda1380_set_recvol(rec_gain, rec_gain, rec_volume);
                break;

            case PCMREC_GOT_DATA:
                pcmrec_callback(false);
                break;

            case SYS_USB_CONNECTED:
                if (!is_recording && !is_stopping)
                {
                    usb_acknowledge(SYS_USB_CONNECTED_ACK);
                    usb_wait_for_disconnect(&pcmrec_queue);                    
                }
                
                break;
        }
    }
    
    logf("thread pcmrec done");
}


