/***************************************************************************
 *             __________               __   ___.
 *   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 <stdlib.h>
#include "menu.h"
#include "lcd.h"
#include "button.h"
#include "mas.h"
#include "system.h"
#include "oscillosgraph.h"

#define SCALE 0x200

/* The different drawing modes */
#define DRAW_MODE_FILLED  0
#define DRAW_MODE_OUTLINE 1
#define DRAW_MODE_PIXEL   2
#define DRAW_MODE_COUNT 3

/* number of ticks between two volume samples */
static int  speed = 1;
/* roll == true -> lcd rolls */
static bool roll = true;
/* see DRAW_MODE_XXX constants for valid values */
static int  drawMode = DRAW_MODE_FILLED;

/**
 * Displays a vertically scrolling oscillosgraph using
 * hardware scrolling of the display. The user can change
 * speed
 */
Menu oscillosgraph(void){
    /* stores current volume value left */
    int  left;
    /* stores current volume value right */
    int  right;
    /* specifies the current position on the lcd */
    int  y = LCD_WIDTH - 1;
    /* buffer the last user input */
    int  button = 0;

    /* only needed when drawing lines */
    int  lastLeft = 0;
    int  lastRight = 0;
    int  lasty = 0;



    /* the main loop */
    while (button != BUTTON_OFF){

        /* read the volume info from MAS */
        left = mas_codec_readreg(0xC) / SCALE;
        left = MIN(left, LCD_WIDTH / 2 - 1);

        right = mas_codec_readreg(0xD) / SCALE;
        right = MIN(right, LCD_WIDTH / 2 - 1);

        /* delete current line. I believe that lcd_clearrect may
           be faster than lcd_clearline because it might plot 
           servaral pixel at once. At least it would be easier to
           optimize a drawing routine that can be reduced to
           vertical / horizontal directions while lcd_drawline
           must always be capable to draw lines in any angle. */
        lcd_clearrect(0, y, LCD_WIDTH, 1);

        switch (drawMode) {
            case DRAW_MODE_FILLED:
                /* Here again I hope to increase performance by
                   using orthogonal drawing functions. */
                lcd_fillrect(LCD_WIDTH / 2 + 1, y, right, 1);
                lcd_fillrect(LCD_WIDTH / 2 - left, y, left, 1);
                break;

            case DRAW_MODE_OUTLINE:
                /* last position needed for lines */
                lasty = MAX(y-1, 0);

                /* Here real lines were neccessary because 
                   anything else was ugly. */
                lcd_drawline(LCD_WIDTH / 2 + right     , y, 
                             LCD_WIDTH / 2 + lastRight , lasty);
                lcd_drawline(LCD_WIDTH / 2 - left    , y,
                             LCD_WIDTH / 2 - lastLeft, lasty);

                /* have to store the old values for drawing lines
                   the next time */
                lastRight = right;
                lastLeft = left;
                break;

            case DRAW_MODE_PIXEL:
                /* straight and simple */
                lcd_drawpixel(LCD_WIDTH / 2 + right, y);
                lcd_drawpixel(LCD_WIDTH / 2 - left, y);
                break;
        }


        /* increment and adjust the drawing position */
        y++;
        if (y >=  LCD_HEIGHT) {
            y = 0;
        }

        /* I roll before update because otherwise the new
           line would appear at the wrong end of the display */
        if (roll) {
            lcd_v_roll(y);
        }

        /* now finally make the new sample visible */
        lcd_update_rect(0, MAX(y-1, 0), LCD_WIDTH, 2);

        /* There are two mechanisms to alter speed:
           1.) slowing down is achieved by increasing
               the time waiting for user input. This
               mechanism uses positive values.
           2.) speeding up is achieved by leaving out
               the user input check for (-speed) volume
               samples. For this mechanism negative values
               are used.
        */
               
        if (speed >= 0 || (speed < 0 && !(y % (-speed)))) {
            char buf[12];

            /* speed values > 0 slow the oszi down. By user input
               speed might become < 1. If a value < 1 was
               passed user input would be disabled. Thus
               it must be ensured that at least 1 is passed. */
            button = button_get_w_tmo(MAX(speed, 1));

            /* react to user input */
            switch (button) {
                case BUTTON_UP:
                    speed ++;
                    snprintf(buf, sizeof buf, "Speed: %d", -speed);
                    lcd_putsxy(0, (y + LCD_HEIGHT - 8) % LCD_HEIGHT, buf, 0);
                    lcd_update_rect(0, (y + LCD_HEIGHT - 8) % LCD_HEIGHT, 
                                    LCD_WIDTH, 8);
                    break;

                case BUTTON_DOWN:
                    speed --;
                    snprintf(buf, sizeof buf, "Speed: %d", -speed);
                    lcd_putsxy(0, (y + LCD_HEIGHT - 8) % LCD_HEIGHT, buf, 0);
                    lcd_update_rect(0, (y + LCD_HEIGHT - 8) % LCD_HEIGHT, 
                                    LCD_WIDTH, 8);
                    break;

                case BUTTON_PLAY:
                    /* pause the demo */
                    button_get(true);
                    break;

                case BUTTON_F1:
                    /* toggle rolling */
                    roll = !roll;
                    break;

                case BUTTON_F2:
                    /* step through the display modes */
                    drawMode ++;
                    drawMode = drawMode % DRAW_MODE_COUNT;

                    /* lcd buffer might be rolled so that
                       the transition from LCD_HEIGHT to 0 
                       takes place in the middle of the screen.
                       That produces ugly results in DRAW_MODE_OUTLINE
                       mode. If rolling is enabled this change will
                       be reverted before the next update anyway.*/
                    lcd_v_roll(0);
                    break;

                case BUTTON_F3:
                    speed = 1;
                    snprintf(buf, sizeof buf, "Speed: %d", -speed);
                    lcd_putsxy(0, (y + LCD_HEIGHT - 8) % LCD_HEIGHT, buf, 0);
                    lcd_update_rect(0, (y + LCD_HEIGHT - 8) % LCD_HEIGHT, 
                                    LCD_WIDTH, 8);
                    break;
            }
        }    
    }

    /* restore to default roll position. 
       Looks funny if you forget to do this... */
    lcd_v_roll(0);
    lcd_update();

    /* standard return */
    return MENU_OK;
}

