/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2002 Philipp Pertermann
 *
 * 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 <string.h>
#include "sprintf.h"
#include "system.h"
#include "kernel.h"
#include "spliteditor.h"
#include "mpeg.h"
#include "lcd.h"
#include "button.h"
#include "mas.h"
#include "id3.h"
#include "key_scheme.h"

#define OSCI_HEIGHT (LCD_HEIGHT -16)

static struct mp3entry* id3 = NULL;
static unsigned int range_start = 0;
static unsigned int range_end = 0;
static unsigned int play_start = 0;
static unsigned int play_end = 0;
static int split_x = LCD_WIDTH / 2;
static unsigned char osci_buffer[LCD_WIDTH];
static bool osci_valid = false;
static unsigned int validation_start = ~(unsigned int)0;
static int loop_mode = 0;
static bool quit_requested = false;

#define LOOP_MODE_ALL  0
#define LOOP_MODE_FROM 1
#define LOOP_MODE_TO   2

#define MIN_RANGE_SIZE 1000

/* Format time into buf.
 *
 * buf      - buffer to format to.
 * buf_size - size of buffer.
 * time     - time to format, in milliseconds.
 */
static void format_time_ms(char* buf, int buf_size, int time)
{
    snprintf(buf, buf_size, "%d:%02d:%03d", time / 60000, 
        time % 60000 / 1000, (time % 60000) % 1000);
}


static int xpos_to_time(int xpos) {
    int retval = 0;
    int range = range_end - range_start;
    retval = range_start + ((xpos * range) / LCD_WIDTH);
    return retval;
}

static int time_to_xpos(int time) {
    int retval = 0;
    int range = range_end - range_start;
    retval = ((time - range_start) * LCD_WIDTH) / range ;
    return retval;
}

static void update_data(void) {
    char buf[20];

    lcd_clearrect(0, 0, LCD_WIDTH, LCD_HEIGHT - OSCI_HEIGHT);

    /* range length */
    format_time_ms(buf, sizeof buf, range_end - range_start);
    lcd_puts(0, 0, buf);
#if 0
    /* osci valid */
    if (osci_valid) {
        lcd_putsxy(0, 8, "valid");
    } else {
        lcd_putsxy(0, 8, "invalid");
    }
    format_time_ms(buf, sizeof buf, validation_start);
    lcd_getstringsize(buf, &width, &height);
    lcd_putsxy(24, 8, buf);
#endif

    /* split point */
    lcd_puts(0, 1, "Split at:");
    format_time_ms(buf, sizeof buf, xpos_to_time(split_x));
    lcd_puts(10, 1, buf);

    lcd_update_rect(0, 0, LCD_WIDTH, LCD_HEIGHT - OSCI_HEIGHT);
}

static void invalidate_osci(void) {
    osci_valid = false;
    validation_start = ~(unsigned int)0;
}

int get_loop_mode(void){
    return loop_mode;
}

void set_loop_mode(int mode) {
    /* range restriction */
    loop_mode = mode % (LOOP_MODE_TO + 1);
    switch (loop_mode) {
        case LOOP_MODE_ALL:
            play_start = range_start;
            play_end = range_end;
            break;
            
        case LOOP_MODE_FROM:
            play_start = xpos_to_time(split_x);
            play_end = range_end;
            break;

        case LOOP_MODE_TO:
            play_start = range_start;
            play_end = xpos_to_time(split_x);
            break;
    }
}

static void redraw_osci(void) {
    int x;
    for (x = 0; x < LCD_WIDTH; x++) {
        if (osci_buffer[x] > 0) {
            lcd_drawline(x, LCD_HEIGHT - 1, x, LCD_HEIGHT - osci_buffer[x]);
        }
    }
    lcd_invertrect(split_x,  LCD_HEIGHT - OSCI_HEIGHT, 1, OSCI_HEIGHT);
}

/*
static void update_osci(void) {
    lcd_clearrect(0, LCD_HEIGHT - OSCI_HEIGHT, LCD_WIDTH, OSCI_HEIGHT);
    redraw_osci();
    lcd_update_rect(0, LCD_HEIGHT - OSCI_HEIGHT, LCD_WIDTH, OSCI_HEIGHT);
}
*/

static void set_range(unsigned int split_time, unsigned int range) {
    if (id3 != NULL) {
        if (range < MIN_RANGE_SIZE) {
            range = MIN_RANGE_SIZE;
        }
        range_start = (split_time > range / 2) ? (split_time - range / 2) : 0;
        range_end = MIN(range_start + range, id3->length);
        split_x = time_to_xpos(split_time);
        invalidate_osci();
        set_loop_mode(LOOP_MODE_ALL);
        update_data();
    }
}

static void zoom(int counter, int denominator) {
    unsigned char oldbuf[LCD_WIDTH];
    int oldrange = range_end - range_start;
    int range = oldrange * counter / denominator;
    int i;
    int oldindex;
    int oldsplitx;
    int splitx;
    unsigned int split;
    memcpy(&oldbuf, &osci_buffer, LCD_WIDTH);

    oldsplitx = split_x;
    split = xpos_to_time(split_x);

    set_range(split, range);
    range = range_end - range_start;

    splitx = time_to_xpos(split);

    for (i = 0; i < LCD_WIDTH; i++) {
        oldindex = (i - splitx) * range / oldrange + oldsplitx ;
        if (oldindex >= 0 && oldindex < LCD_WIDTH) {
            osci_buffer[i] = oldbuf[oldindex];
        } else {
            osci_buffer[i] = 0;
        }
    }

    split_x = time_to_xpos(split);
    redraw_osci();
    invalidate_osci();
}

int get_split_x(void){
    return split_x;
}

void set_split_x(int newx) {
    lcd_invertrect (split_x, LCD_HEIGHT - OSCI_HEIGHT, 1, OSCI_HEIGHT);
    lcd_update_rect(split_x, LCD_HEIGHT - OSCI_HEIGHT, 1, OSCI_HEIGHT);

    if (newx >= 0 && newx < LCD_WIDTH) {
        split_x = newx;
        set_loop_mode(loop_mode);
        update_data();
    }
    lcd_invertrect (split_x, LCD_HEIGHT - OSCI_HEIGHT, 1, OSCI_HEIGHT);
    lcd_update_rect(split_x, LCD_HEIGHT - OSCI_HEIGHT, 1, OSCI_HEIGHT);
}

void split_zoom_in(void) {
    lcd_clearrect(0, LCD_HEIGHT - OSCI_HEIGHT, LCD_WIDTH, OSCI_HEIGHT);
    zoom(3, 4);
    lcd_update_rect(0, LCD_HEIGHT - OSCI_HEIGHT, LCD_WIDTH, OSCI_HEIGHT);
    update_data();
}

void split_zoom_out(void){
    lcd_clearrect(0, LCD_HEIGHT - OSCI_HEIGHT, LCD_WIDTH, OSCI_HEIGHT);
    zoom(4, 3);
    lcd_update_rect(0, LCD_HEIGHT - OSCI_HEIGHT, LCD_WIDTH, OSCI_HEIGHT);
    update_data();
}

void split_quit(void) {
    quit_requested = true;
}
                    
unsigned int split_editor(struct mp3entry * id3_to_split,
                          unsigned int split_time, 
                          unsigned int range) {
    int button = BUTTON_NONE;
//    bool paused = false;
//    int pitch = 100;
    id3 = id3_to_split;
    unsigned int last_elapsed = 0;
    int lastx = LCD_WIDTH -1;

    if (id3 != NULL) {
        set_range(split_time, range);
        quit_requested = false;
        while (!quit_requested) {

            /* get position */
            unsigned int elapsed = id3->elapsed;
            int x = time_to_xpos(elapsed);

            /* are we still in the zoomed range? */
            if (elapsed > play_start && elapsed < play_end) {
                /* read volume info */
                unsigned short volume = mas_codec_readreg(MAS_REG_DQPEAK_L);
                volume += mas_codec_readreg(MAS_REG_DQPEAK_R);
                volume = (volume * OSCI_HEIGHT) / (2 * MAX_PEAK);

                /* update osci_buffer */
                if (osci_valid || lastx == x) {
                    osci_buffer[x] = MAX(osci_buffer[x], volume);
                } else {
                    osci_buffer[x] = volume;
                }

                /* make room */
                lcd_clearrect(x, LCD_HEIGHT - OSCI_HEIGHT, 1, OSCI_HEIGHT);

                /* draw a value */
                if (osci_buffer[x] > 0) {
                    lcd_drawline (x, LCD_HEIGHT - 1, x, LCD_HEIGHT - osci_buffer[x]);
                }

                /* mark the split point */
                if (x == split_x) {
                    lcd_invertrect(split_x, LCD_HEIGHT - OSCI_HEIGHT, 1, OSCI_HEIGHT);
                }

                /* mark the current position */
                lcd_invertrect(x, LCD_HEIGHT - OSCI_HEIGHT, 1, OSCI_HEIGHT);
                if (lastx != x) {
                    lcd_invertrect(lastx, LCD_HEIGHT - OSCI_HEIGHT, 1, OSCI_HEIGHT);
                }

                /* make visible */
                lcd_update_rect(x, LCD_HEIGHT - OSCI_HEIGHT, 1, OSCI_HEIGHT);
                lcd_update_rect(lastx, LCD_HEIGHT - OSCI_HEIGHT, 1, OSCI_HEIGHT);

                lastx = x;
            } 
            
            /* we're not in the zoom range -> rewind */
            else {
                if (elapsed >= play_end) {
                    switch (loop_mode) {
                        case LOOP_MODE_ALL:
                        case LOOP_MODE_TO:
                            mpeg_ff_rewind(-(elapsed - range_start));
                            break;

                        case LOOP_MODE_FROM:
                            mpeg_ff_rewind(-(elapsed - xpos_to_time(split_x)));
                            break;
                    }
                }
            }
            
            button = button_get(false);
            yield();

            key_scheme_current = select_and_execute(button, key_scheme_current);
/*
            switch (button) {
                case BUTTON_F2:
                    set_loop_mode(loop_mode + 1);
                    break;

                case BUTTON_OFF:
                    quit_requested = true;
                    break;

                case BUTTON_PLAY:
                    if (paused) {
                        mpeg_resume();
                    } else {
                        mpeg_pause();
                    }
                    paused = !paused;
                    break;

            case BUTTON_ON | BUTTON_RIGHT:
            case BUTTON_ON | BUTTON_RIGHT | BUTTON_REPEAT:
                if (pitch < 200) {
                    pitch++;
#ifdef HAVE_MAS3587F
                    mpeg_set_pitch(pitch);
#endif
                }
                break;

            case BUTTON_ON | BUTTON_LEFT:
            case BUTTON_ON | BUTTON_LEFT | BUTTON_REPEAT:
                if (pitch > 50) {
                    pitch--;
#ifdef HAVE_MAS3587F
                    mpeg_set_pitch(pitch);
#endif
                }
                break;

            case BUTTON_ON | BUTTON_PLAY:
                if (pitch != 100) {
                    pitch = 100;
#ifdef HAVE_MAS3587F
                    mpeg_set_pitch(pitch);
#endif
                }
                break;


            }
*/

            if (validation_start == ~(unsigned int)0) {
                if (elapsed < range_end && elapsed > range_start) {
                    validation_start = elapsed;
                } else {
                    int endx = time_to_xpos(range_end);
                    validation_start = xpos_to_time(endx - 2);
                }
                last_elapsed = elapsed + 1;
            } else {
                if ((last_elapsed <= validation_start) && (elapsed > validation_start)) {
                    osci_valid = true;
                }

                last_elapsed = elapsed;
            }
            update_data();
        }

    }
    return xpos_to_time(split_x);
}



void split_editor_main(void) {
    /* dummy inits, belong in main */
    load_scheme(NULL);

    lcd_clear_display();
    lcd_update();
    lcd_setmargins(0,0);
    id3 = mpeg_current_track();
    if (id3 != NULL) {
        split_editor(id3, id3->elapsed, MIN_RANGE_SIZE * 8);
    }
}

