
/*******************************************************************************/
/* Copyright (C) 2007-2008 Jonathan Moore Liles                                */
/*                                                                             */
/* 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 program is distributed in the hope that it will be useful, but WITHOUT */
/* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       */
/* FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for   */
/* more details.                                                               */
/*                                                                             */
/* You should have received a copy of the GNU General Public License along     */
/* with This program; see the file COPYING.  If not,write to the Free Software */
/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
/*******************************************************************************/

/* This is a generic double-buffering, optimizing canvas interface to
   grids (patterns and phrases). It draws only what is necessary to keep
   the display up-to-date. Actual drawing functions are in draw.C */

#include "canvas.H"
#include "pattern.H"
#include "gui/draw.H"
#include "common.h"

#include "non.H"

cell_t **
Canvas::_alloc_array ( void )
{
    cell_t **a;

    int one = sizeof( typeof( a ) ) * m.vp->w;
    int two = sizeof( typeof( a[0] ) ) * (m.vp->h * m.vp->w);

    a = (cell_t **) malloc( one + two );

    m.size = one + two;

    cell_t *c = (cell_t *) (((unsigned char *)a) + one);

    for ( uint x = m.vp->w; x-- ; )
    {
        a[x] = c;
        c += m.vp->h;
        for ( uint y = m.vp->h; y-- ; )
        {
            a[ x ][ y ].flags = 0;
            a[ x ][ y ].state = -1;
            a[ x ][ y ].color = 0;
        }
    }

    m.w = m.vp->w;
    m.h = m.vp->h;

    return a;
}

Canvas::Canvas ( )
{
    m.origin_x = m.origin_y = m.height = m.width = m.div_w = m.div_h = m.playhead = m.margin_top = m.margin_left = m.playhead = m.w = m.h = m.p1 = m.p2 = m.p3 = m.p4 = 0;

    m.margin_top = ruler_height;

    m.draw = false;
    m.ruler_drawn = false;
    m.mapping_drawn = false;
    m.grid_drawn = false;

    m.current = m.previous = NULL;

    m.row_compact = true;

    m.maxh = 128;

    m.vp = NULL;
}

void
Canvas::handle_event_change ( void )
{
    /* mark the song as dirty and pass the signal on */
    song.set_dirty();

    signal_draw();
}

/** change grid to /g/, returns TRUE if new grid size differs from old */
void
Canvas::grid ( Grid *g )
{
    m.grid = g;

    if ( ! g )
        return;

    m.vp = &g->viewport;

    char *s = m.vp->dump();
    DMESSAGE( "viewport: %s", s );
    free( s );

    m.ruler_drawn = false;

    resize_grid();

    update_mapping();

//    m.shape = m.grid->draw_shape();

    /* connect signals */
    /* FIXME: what happens when we do this twice? */
    g->signal_events_change.connect( mem_fun( this, &Canvas::handle_event_change ) );
    g->signal_settings_change.connect( signal_settings_change.make_slot() );

    signal_draw();
    signal_settings_change();
    signal_pan();
}

/** keep row compaction tables up-to-date */
void
Canvas::_update_row_mapping ( void )
{
    /* reset */
    for ( int i = 128; i-- ; )
        m.rtn[i] = m.ntr[i] = -1;

    DMESSAGE( "updating row mapping" );

    /* rebuild */
    int r = 0;
    for ( int n = 0; n < 128; ++n )
    {
        if ( m.grid->row_name( n ) )
        {
            m.rtn[r] = n;
            m.ntr[n] = r;
            ++r;
        }
    }

    if ( m.row_compact && r )
        m.maxh = r;
    else
        m.maxh = 128;

    m.vp->h = min( m.vp->h, m.maxh );
}

/** update everything about mapping, leaving the viewport alone */
void
Canvas::update_mapping ( void )
{
    _update_row_mapping();

    m.mapping_drawn = false;

    resize();

    int old_margin = m.margin_left;

    m.margin_left = 0;

    m.draw = false;

    m.grid->draw_row_names( this );

    m.draw = true;

    if ( m.margin_left != old_margin )
    {
        signal_resize();
        signal_draw();
    }
    else
        signal_draw();

}

/** change grid mapping */
void
Canvas::changed_mapping ( void )
{
    update_mapping();

    m.vp->h = min( m.vp->h, m.maxh );

    if ( m.vp->y + m.vp->h > m.maxh )
        m.vp->y = (m.maxh / 2) - (m.vp->h / 2);

    signal_pan();
}

Grid *
Canvas::grid ( void )
{
    return m.grid;
}


/** recalculate node sizes based on physical dimensions */
void
Canvas::resize ( void )
{
    if ( ! m.vp )
        return;

    m.div_w = (m.width - m.margin_left) / m.vp->w;
    m.div_h = (m.height - m.margin_top) / m.vp->h;

    m.mapping_drawn = m.ruler_drawn = false;
}

/** reallocate buffers to match grid dimensions */
void
Canvas::resize_grid ( void )
{
    //   _update_row_mapping();

    resize();

    if ( m.vp )
    {
        if ( m.vp->w != m.w || m.vp->h != m.h ||
            m.div_w != m.old_div_w || m.div_h != m.old_div_h )
        {
            if ( m.grid_drawn )
                signal_resize();

            m.old_div_w = m.div_w;
            m.old_div_h = m.div_h;
        }
        else
            return;
    }

    DMESSAGE( "resizing grid %dx%d", m.vp->w, m.vp->h );

    if ( m.previous )
    {
        free( m.previous );
        free( m.current );
    }

    m.current = _alloc_array();
    m.previous = _alloc_array();

    m.grid_drawn = false;
}

/** inform the canvas with new phsyical dimensions */
void
Canvas::resize ( int x, int y, int w, int h )
{
    m.origin_x = x;
    m.origin_y = y;

    m.width = w;
    m.height = h;

    resize();
}



/***********/
/* Drawing */
/***********/

/** copy last buffer into current */
void
Canvas::copy ( void )
{
    for ( uint y = m.vp->h; y-- ; )
        for ( uint x = m.vp->w; x-- ; )
            m.current[ x ][ y ] = m.previous[ x ][ y ];
}


/** reset last buffer */
void
Canvas::_reset ( void )
{
    cell_t empty = {0,0,0};

    for ( uint y = m.vp->h; y-- ; )
        for ( uint x = m.vp->w; x-- ; )
            m.current[ x ][ y ] = empty;
}

/** prepare current buffer for drawing (draw "background") */
void
Canvas::clear ( void )
{
    uint rule = m.grid->ppqn();

    uint lx = m.grid->ts_to_x( m.grid->length() );

    for ( uint y = m.vp->h; y--; )
        for ( uint x = m.vp->w; x--; )
        {
            m.current[ x ][ y ].color = 0;
            m.current[ x ][ y ].state = EMPTY;
            m.current[ x ][ y ].flags = 0;
        }

    for ( int x = m.vp->w - rule; x >= 0; x -= rule )
        for ( uint y = m.vp->h; y-- ; )
            m.current[ x ][ y ].state = LINE;

    int sx = (int)(lx - m.vp->x) >= 0 ? lx - m.vp->x : 0;

    for ( int x = sx; x < m.vp->w; ++x )
        for ( int y = m.vp->h; y-- ; )
            m.current[ x ][ y ].state = PARTIAL;

}

/** is /x/ within the viewport? */
bool
Canvas::viewable_x ( int x )
{
    return x >= m.vp->x && x < m.vp->x + m.vp->w;
}

/** flush delta of last and current buffers to screen, then flip them */
void
Canvas::flip ( void )
{
    /* FIXME: should this not go in clear()? */
    if ( m.p1 != m.p2 )
    {
        if ( viewable_x( m.p1 ) ) draw_line( m.p1 - m.vp->x, F_P1 );
        if ( viewable_x( m.p2 ) ) draw_line( m.p2 - m.vp->x, F_P2 );
    }

    if ( viewable_x( m.playhead ) ) draw_line( m.playhead - m.vp->x, F_PLAYHEAD );

    const int shape = m.grid->draw_shape();

    for ( uint y = m.vp->h; y--; )
        for ( uint x = m.vp->w; x--; )
        {
            cell_t *c = &m.current[ x ][ y ];
            cell_t *p = &m.previous[ x ][ y ];

            /* draw selection rect */
            if ( m.p3 != m.p4 )
                if ( y + m.vp->y >= m.p3 && x + m.vp->x >= m.p1 &&
                     y + m.vp->y <= m.p4 && x + m.vp->x < m.p2 )
                    c->flags |= F_SELECTION;

            if ( *c != *p )
                gui_draw_shape( m.origin_x + m.margin_left + x * m.div_w, m.origin_y + m.margin_top + y * m.div_h, m.div_w, m.div_h,
                                shape, c->state, c->flags, c->color );
        }

    cell_t **tmp = m.previous;

    m.previous = m.current;
    m.current = tmp;
}

/** redraw the ruler at the top of the canvas */
void
Canvas::redraw_ruler ( void )
{
    m.margin_top = gui_draw_ruler( m.origin_x + m.margin_left, m.origin_y, m.vp->w, m.div_w, m.grid->division(), m.vp->x,
                                   m.p1 - m.vp->x, m.p2 - m.vp->x );
    m.ruler_drawn = true;
}

/** callback called by Grid::draw_row_names() to draw an individual row name  */
void
Canvas::draw_row_name ( int y, const char *name, int color )
{
    bool draw = m.draw;
    bool clear = false;

    y = ntr( y );

    if ( ! m.row_compact && ! name )
        clear = true;

    y -= m.vp->y;

    int bx = m.origin_x;
    int by = m.origin_y + m.margin_top + y * m.div_h;
    int bw = m.margin_left;
    int bh = m.div_h;

    if ( y < 0 || y >= m.vp->h )
        draw = false;

    if ( clear && draw )
        gui_clear_area( bx, by, bw, bh );
    else
        m.margin_left = max( m.margin_left, gui_draw_string( bx, by,
                                                             bw, bh,
                                                             color,
                                                             name,
                                                             draw ) );
}

/** redraw row names */
void
Canvas::redraw_mapping ( void )
{
    m.margin_left = 0;

    m.draw = false;

    m.grid->draw_row_names( this );

    resize();

    m.draw = true;

    m.grid->draw_row_names( this );

    m.mapping_drawn = true;
}

void
Canvas::draw_mapping ( void )
{
    if ( ! m.mapping_drawn ) redraw_mapping();
}

void
Canvas::draw_ruler ( void )
{
    if ( ! m.ruler_drawn ) redraw_ruler();
}

/** "draw" a shape in the backbuffer */
void
Canvas::draw_shape ( int x, int y, int shape, int state, int color, bool selected )
{
    y = ntr( y );

    if ( y < 0 )
        return;

    // adjust for viewport.

    x -= m.vp->x;
    y -= m.vp->y;

    if ( x < 0 || y < 0 || x >= m.vp->w || y >= m.vp->h )
        return;

    m.current[ x ][ y ].color = color;
    m.current[ x ][ y ].state = (uint)m.vp->x + x > m.grid->ts_to_x( m.grid->length() ) ? PARTIAL : state;
    if ( selected )
        m.current[ x ][ y ].state = SELECTED;
    m.current[ x ][ y ].flags = 0;
}

/** callback used by Grid::draw()  */
void
Canvas::draw_dash ( int x, int y, int l, int shape, int color, bool selected )
{
    draw_shape( x, y, shape, FULL, color, selected );
    for ( int i = x + l - 1; i > x; i-- )
    {
        draw_shape( i, y, shape, CONTINUED, 0, selected );
    }
}

/** draw a vertical line with flags */
void
Canvas::draw_line ( int x, int flags )
{
    for ( uint y = m.vp->h; y-- ; )
        m.current[ x ][ y ].flags |= flags;
}

int
Canvas::playhead_moved ( void )
{
    int x = m.grid->ts_to_x( m.grid->index() );
    
    return m.playhead != x;
}

/** draw only the playhead--without reexamining the grid */
int
Canvas::draw_playhead ( void )
{
    int x = m.grid->ts_to_x( m.grid->index() );

    if ( m.playhead == x )
        return 0;

    m.playhead = x;

    if ( m.playhead < m.vp->x || m.playhead >= m.vp->x + m.vp->w )
    {
        if ( config.follow_playhead )
        {
            m.vp->x = m.playhead / m.vp->w * m.vp->w;

            m.ruler_drawn = false;

            signal_draw();

            return 0;
        }
    }

    copy();

    for ( uint x = m.vp->w; x-- ; )
        for ( uint y = m.vp->h; y-- ; )
            m.current[ x ][ y ].flags &= ~ (F_PLAYHEAD | F_P1 | F_P2 );

    flip();

    /* actually if we're recording, we should draw the grid once per
     * playhead movement also */
    if ( pattern::recording() == m.grid )
    {
        draw();
    }

    return 1;
}

/** draw ONLY those nodes necessary to bring the canvas up-to-date with the grid */
void
Canvas::draw ( void )
{
    DMESSAGE( "drawing canvas" );

    draw_mapping();
    draw_ruler();

    m.grid_drawn = true;

    m.grid->draw( this, m.vp->x, m.vp->y, m.vp->w, m.vp->h );
}

/** redraw every node on the canvas from the buffer (without
 * necessarily reexamining the grid) */
void
Canvas::redraw ( void )
{
    DMESSAGE( "redrawing canvas" );

    if ( ! m.grid_drawn )
        draw();

    m.ruler_drawn = false;
    m.mapping_drawn = false;

    draw_mapping();
    draw_ruler();

    const int shape = m.grid->draw_shape();

    for ( int y = m.vp->h; y--; )
        for ( int x = m.vp->w; x--; )
        {
            cell_t c = m.previous[ x ][ y ];

            if ( m.vp->x + x == m.playhead )
                c.flags |= F_PLAYHEAD;

            gui_draw_shape( m.origin_x + m.margin_left + x * m.div_w, m.origin_y + m.margin_top + y * m.div_h, m.div_w, m.div_h,
                            shape, c.state, c.flags, c.color );
        }
}

/** convert pixel coords into grid coords. returns true if valid */
bool
Canvas::grid_pos ( int *x, int *y ) const
{
    *y = (*y - m.margin_top - m.origin_y) / m.div_h;
    *x = (*x - m.margin_left - m.origin_x) / m.div_w;

    if ( *x < 0 || *y < 0 || *x >= m.vp->w || *y >= m.vp->h )
        return false;

    /* adjust for viewport */
    *x += m.vp->x;
    *y += m.vp->y;

    /* adjust for row-compaction */
    *y = rtn( *y );

    return true;
}



/******************/
/* Input handlers */
/******************/

/* These methods translate viewport pixel coords to absolute grid
   coords and pass on to the grid. */

/** if coords correspond to a row name entry, return the (absolute) note number, otherwise return -1 */
int
Canvas::is_row_name ( int x, int y )
{
    if ( x - m.origin_x >= m.margin_left )
        return -1;

    x = m.margin_left;

    grid_pos( &x, &y );

    return m.grid->y_to_note( y );
}

void
Canvas::start_cursor ( int x, int y )
{
    if ( ! grid_pos( &x, &y ) )
        return;

    m.ruler_drawn = false;

    m.p1 = x;
    m.p3 = ntr( y );

    _lr();

    signal_draw();
}

void
Canvas::end_cursor ( int x, int y )
{
    if ( ! grid_pos( &x, &y ) )
        return;

    m.ruler_drawn = false;

    m.p2 = x;
    m.p4 = ntr( y );

    _lr();

    signal_draw();
}

void
Canvas::set ( int x, int y )
{
    if ( y - m.origin_y < m.margin_top )
        /* looks like a click on the ruler */
    {
        if ( x - m.margin_left - m.origin_x >= 0 )
        {
            m.p1 = m.vp->x + ((x - m.margin_left - m.origin_x) / m.div_w);
            m.ruler_drawn = false;

            m.p3 = m.p4 = 0;
        }

        _lr();

        signal_draw();

        return;
    }

    if ( ! grid_pos( &x, &y ) )
        return;

    m.grid->put( x, y, 0 );
}

void
Canvas::unset ( int x, int y )
{
    if ( y - m.origin_y < m.margin_top )
        /* looks like a click on the ruler */
    {
        if ( x - m.margin_left - m.origin_x >= 0 )
        {
            m.p2 = m.vp->x + ((x - m.margin_left - m.origin_x) / m.div_w);
            m.ruler_drawn = false;

            m.p3 = m.p4 = 0;
        }

        _lr();

        signal_draw();

        return;
    }

    if ( ! grid_pos( &x, &y ) )
        return;

    m.grid->del( x, y );
}

void
Canvas::adj_color ( int x, int y, int n )
{
    if ( ! grid_pos( &x, &y ) )
        return;

    m.grid->adj_velocity( x, y, n );
}

void
Canvas::adj_length ( int x, int y, int n )
{
    if ( ! grid_pos( &x, &y ) )
        return;

    m.grid->adj_duration( x, y, n );
}

void
Canvas::select ( int x, int y )
{
    if ( ! grid_pos( &x, &y ) )
        return;

    m.grid->toggle_select( x, y );
}

void
Canvas::move_selected ( int dir, int n )
{
    switch ( dir )
    {
        case RIGHT:
            m.grid->move_selected( n );
            break;
        case LEFT:
            m.grid->move_selected( 0 - n );
            break;
        case UP:
        case DOWN:
        {
            /* row-compaction makes this a little complicated */
            event_list *el = m.grid->events();

            /* FIXME: don't allow movement beyond the edges!  */

/*             int hi, lo; */

/*             m.grid->selected_hi_lo_note( &hi, &lo ); */

/*             hi = ntr( hi ) > 0 ? ntr( hi ) :  */

/*             if ( m.grid->y_to_note( ntr( hi ) ) ) */


            if ( dir == UP )
                for ( int y = 0; y <= m.maxh; ++y )
                    el->rewrite_selected( m.grid->y_to_note( rtn( y ) ), m.grid->y_to_note( rtn( y - n ) ) );
            else
                for ( int y = m.maxh; y >= 0; --y )
                    el->rewrite_selected( m.grid->y_to_note( rtn( y ) ), m.grid->y_to_note( rtn( y + n ) ) );

            m.grid->events( el );

            delete el;
            break;
        }
    }
}

void
Canvas::randomize_row ( int y )
{
    int x = m.margin_left;

    if ( ! grid_pos( &x, &y ) )
        return;

    ((pattern*)m.grid)->randomize_row( y, song.random.feel, song.random.probability );
}

void
Canvas::_lr ( void )
{
    int l, r;

    if ( m.p2 > m.p1 )
    {
        l = m.p1;
        r = m.p2;
    }
    else
    {
        l = m.p2;
        r = m.p1;
    }

    m.p1 = l;
    m.p2 = r;
}

void
Canvas::select_range ( void )
{
    if ( m.p3 == m.p4 )
        m.grid->select( m.p1, m.p2 );
    else
        m.grid->select( m.p1, m.p2, rtn( m.p3 ), rtn( m.p4 ) );
}

void
Canvas::invert_selection ( void )
{
    m.grid->invert_selection();
}

void
Canvas::crop ( void )
{
    if ( m.p3 == m.p4 )
        m.grid->crop( m.p1, m.p2 );
    else
        m.grid->crop( m.p1, m.p2, rtn( m.p3 ), rtn( m.p4 ) );

    m.vp->x = 0;

    m.p2 = m.p2 - m.p1;
    m.p1 = 0;

    m.ruler_drawn = false;
}

void
Canvas::delete_time ( void )
{
    m.grid->delete_time( m.p1, m.p2 );
}


void
Canvas::insert_time ( void )
{
    m.grid->insert_time( m.p1, m.p2 );
}

/** paste range as new grid */
void
Canvas::duplicate_range ( void )
{
    Grid *g = m.grid->clone();

    g->crop( m.p1, m.p2 );
    g->viewport.x = 0;
}

void
Canvas::row_compact ( int n )
{
    switch ( n )
    {
        case OFF:
            m.row_compact = false;
            m.maxh = 128;
            break;
        case ON:
            m.row_compact = true;
            m.vp->y = 0;
            _update_row_mapping();
            break;
        case TOGGLE:
            row_compact( m.row_compact ? OFF : ON );
            break;
    }
    _reset();
    m.mapping_drawn = false;
}

void
Canvas::pan ( int dir, int n )
{

    switch ( dir )
    {
        case LEFT: case RIGHT: case TO_PLAYHEAD: case TO_NEXT_NOTE: case TO_PREV_NOTE:
            /* handle horizontal movement specially */
            n *= m.grid->division();
            m.ruler_drawn = false;
            break;
        default:
            n *= 5;
            m.mapping_drawn = false;
            break;
    }

    switch ( dir )
    {
        case LEFT:
            m.vp->x = max( m.vp->x - n, 0 );
            break;
        case RIGHT:
            m.vp->x += n;
            break;
        case TO_PLAYHEAD:
            m.vp->x = m.playhead - (m.playhead % m.grid->division());
            break;
        case UP:
            m.vp->y = max( m.vp->y - n, 0 );
            break;
        case DOWN:
            m.vp->y = min( m.vp->y + n, m.maxh - m.vp->h );
            break;
        case TO_NEXT_NOTE:
        {
            int x = m.grid->next_note_x( m.vp->x );
            m.vp->x = x - (x % m.grid->division() );
            break;
        }
        case TO_PREV_NOTE:
        {
            int x = m.grid->prev_note_x( m.vp->x );
            m.vp->x = x - (x % m.grid->division() );
            break;
        }
    }

    signal_draw();
    signal_pan();
}

void
Canvas::can_scroll ( int *left, int *right, int *up, int *down )
{
    *left = m.vp->x;
    *right = -1;
    *up = m.vp->y;
    *down = m.maxh - ( m.vp->y + m.vp->h );
}


/** adjust horizontal zoom (* n) */
void
Canvas::h_zoom ( float n )
{
    m.vp->w = max( 32, min( (int)(m.vp->w * n), 256 ) );

    resize_grid();

    song.set_dirty();
}

void
Canvas::v_zoom_fit ( void )
{
    if ( ! m.grid )
        return;

    changed_mapping();

    m.vp->h = m.maxh;
    m.vp->y = 0;

    resize_grid();

    song.set_dirty();

}

/** adjust vertical zoom (* n) */
void
Canvas::v_zoom ( float n )
{
    m.vp->h = max( 1, min( (int)(m.vp->h * n), m.maxh ) );

    resize_grid();

    song.set_dirty();
}

void
Canvas::notes ( char *s )
{
    m.grid->notes( s );
}

char *
Canvas::notes ( void )
{
    return m.grid->notes();
}
