/*
 * widgets/ScrollbarWidget.cc:
 *
 * Copyright (C) 2000 John Watson, jwatson@tempusmud.com
 * 
 * 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; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 * 
 */

#include "ButtonWidget.h"
#include "ScrollbarWidget.h"
#include "WidgetManager.h"
#include "Viewport.h"

ScrollbarElevatorWidget::ScrollbarElevatorWidget( int w, 
                                                  int h, 
                                                  class ScrollbarWidget *scrollbar ) : ButtonWidget( w, h ) {
        _scrollbar = scrollbar;
        _grabbedAtRelative = -1;
        setButtonColors( getLookAndFeel()->getPrimaryColor( COLOR_TWO ), 0, 0 );
        setDrawBottomHighlight( false );

        _name = "ScrollbarElevatorWidget";
}

void ScrollbarElevatorWidget::paint() {
        ButtonWidget::paint();
        
        float lineVerts[2][4][2] = {
                { { -1,         1 },
                  { -1,         _size[1] - 2 },
                  { _size[0]-1, 1 },
                  { _size[0]-1, _size[1] - 2 }
                },
                { { 1,            0 },
                  { _size[0] - 1, 0 },
                  { 1,            _size[1] },
                  { _size[0] - 1, _size[1] }
                }
        };


        glColor3fv( getLookAndFeel()->getSecondaryColor( COLOR_TWO ) );
        glBegin( GL_LINES );
        {
                glVertex2fv( lineVerts[ _scrollbar->_type ][ 0 ] );
                glVertex2fv( lineVerts[ _scrollbar->_type ][ 1 ] );
                glVertex2fv( lineVerts[ _scrollbar->_type ][ 2 ] );
                glVertex2fv( lineVerts[ _scrollbar->_type ][ 3 ] );
        }
        glEnd();
}

bool ScrollbarElevatorWidget::mousePressed( SDL_MouseButtonEvent *evt ) {
        _grabbedAtRelative = ( _scrollbar->_type == SCROLL_VERTICAL ) ? evt->y : evt->x;
        setInputControllingWidget( this );
        return ButtonWidget::mousePressed( evt );
}

bool ScrollbarElevatorWidget::mouseReleased( SDL_MouseButtonEvent *evt ) {
        _grabbedAtRelative = -1;
        unsetInputControllingWidget( this );
        return ButtonWidget::mouseReleased( evt );
}


bool ScrollbarElevatorWidget::mouseMoved( SDL_MouseMotionEvent *evt ) {
        if ( _grabbedAtRelative >= 0 ) {

                int pos = ( _scrollbar->_type == SCROLL_VERTICAL ) ? 
                        ( (Sint16) evt->y ) : 
                        ( (Sint16) evt->x );
                
                pos -= _grabbedAtRelative;
                
                _scrollbar->attemptSlide( pos );
                return true;
        }

        return false;
}

ScrollbarButtonWidget::ScrollbarButtonWidget( int w, 
                                              int h, 
                                              enum ScrollbarEndType endType,
                                              class ScrollbarWidget * scrollbar ) : ButtonWidget( w, h ) {
        _scrollbar = scrollbar;
        _endType = endType;
        setDrawBottomHighlight( false );
}

void ScrollbarButtonWidget::paint() {
        ButtonWidget::paint();
        
        //
        // [ ScrollType ][ ScrollbarEndType ]
        // horiz near                   
        // horiz far
        // vert near
        // vert far
        //
        static float triangleVerts[2][2][3][2] = { 
                // horiz
                {
                        // near
                        { 
                                { _size[0] / 4,     _size[1] / 2 },
                                { _size[0] * 3 / 4, _size[1] / 4 },
                                { _size[0] * 3 / 4,     _size[1] * 3 / 4 }
                        },
                        // far
                        { 
                                { _size[0] * 3 / 4,  _size[1] / 2 },
                                { _size[0] / 4,      _size[1] * 3 / 4 },
                                { _size[0] / 4,      _size[1] / 4 }
                        }
                },
                // vert
                {
                        // near
                        { 
                                { _size[0] * 3 / 4, _size[1] * 3 / 4 },
                                { _size[0] / 4,     _size[1] * 3 / 4 },
                                { _size[0] / 2,     _size[1] / 4 }
                        },
                        // far
                        { 
                                { _size[0] / 4,     _size[1] / 4 },
                                { _size[0] * 3 / 4, _size[1] / 4 },
                                { _size[0] / 2,     _size[1] * 3 / 4 }
                        }
                }

        };
        
        glColor4fv( getLookAndFeel()->getBlackColor() );

        glBegin( GL_TRIANGLES );
        {
                glVertex2fv( triangleVerts[ _scrollbar->_type ][ _endType ][ 0 ] );
                glVertex2fv( triangleVerts[ _scrollbar->_type ][ _endType ][ 1 ] );
                glVertex2fv( triangleVerts[ _scrollbar->_type ][ _endType ][ 2 ] );
        }
        glEnd();

}

bool ScrollbarButtonWidget::mousePressed( SDL_MouseButtonEvent * evt ) {
        
        _scrollbar->_scrollViewport->scrollUnits( _scrollbar->_type, ( _endType == SB_NEAR ) ? -1 : 1 );
        ButtonWidget::mousePressed( evt );
        WidgetManager::getInstance()->addUpdateWidget( this );
        return true;
}

bool ScrollbarButtonWidget::mouseReleased( SDL_MouseButtonEvent * evt ) {
        if ( isPressed() )
                WidgetManager::getInstance()->removeUpdateWidget( this );

        return ( ButtonWidget::mouseReleased( evt ) );
}

void ScrollbarButtonWidget::updateWidget() {
        if ( isPressed() ) {
                _scrollbar->_scrollViewport->scrollUnits( _scrollbar->_type, ( _endType == SB_NEAR ) ? -1 : 1 );
        }
        else {
                WidgetManager::getInstance()->removeUpdateWidget( this );
        }
}

int ScrollbarWidget::getElevatorSize() {
        
        int numVisPixels = _scrollViewport->getSize()[ _type ];
        
        int totScrollPixels = _scrollViewport->getView()->getSize()[ _type ] - numVisPixels;
        
        int elevatorSize = _railLen * numVisPixels / ( totScrollPixels + numVisPixels );
        
        return max( ScrollbarWidget::MIN_SCROLLBAR_LEN, elevatorSize );
}

void ScrollbarWidget::resetInternals() {
        _railTravelLen = _railLen - _elevatorWidget->getSize()[ _type ];
        
        int totScrollPixels = _scrollViewport->getView()->getSize()[ _type ] - _scrollViewport->getSize()[ _type ];
        
        _pixelRatio =
                ( totScrollPixels > 0 ) ?
                ( (double) _railTravelLen / totScrollPixels ) : 
                _railTravelLen;

        int elevatorPos[2] = { 0, 0 };
        elevatorPos[ _type ] = 
                (int) ( _pixelRatio * _scrollViewport->getViewPosition()[ _type ] ) + _railMargin; 
        
        _elevatorWidget->setPosition( elevatorPos[0], elevatorPos[1] );
}

ScrollbarWidget::ScrollbarWidget( int w, int h, 
                                  enum ScrollType type,
                                  class Viewport * viewport ) : 
        Widget( w, h ), 
        WidgetEventListener()
{

        _name = "ScrollbarWidget";

        setOpaque( true );

        _type = type;
        _scrollViewport = viewport;
        
        //
        // setup event listeners
        //

        acceptEventType( EVENT_VIEWPORT, true );
        acceptEventType( EVENT_RESIZE, true );

        _scrollViewport->addWidgetEventListener( (WidgetEventListener *) this );
        _scrollViewport->getView()->addWidgetEventListener( (WidgetEventListener *) this );

        int majorAxisLen = ( _type == SCROLL_HORIZONTAL ) ? w : h;
        int minorAxisLen = ( _type == SCROLL_HORIZONTAL ) ? h : w;
        
        _railMargin = minorAxisLen;

        _buttons[0] = new ScrollbarButtonWidget( minorAxisLen, minorAxisLen, SB_NEAR, this );
        _buttons[1] = new ScrollbarButtonWidget( minorAxisLen, minorAxisLen, SB_FAR, this );

        _railLen = majorAxisLen - 2 * minorAxisLen;

        int elevatorSize = getElevatorSize();

        if ( _type == SCROLL_VERTICAL ) {
                _elevatorWidget = new ScrollbarElevatorWidget( minorAxisLen, elevatorSize, this );
        }
        else {
                _elevatorWidget = new ScrollbarElevatorWidget( elevatorSize, minorAxisLen, this );
        }

        add( _elevatorWidget, 0, 0 );

        resetInternals();
        
        //        cout << " railtravel: " << _railTravelLen << " pixpertick: " << _pixelRatio  << endl;

        int farButtonPos[2];
        farButtonPos[0] = ( _type == SCROLL_HORIZONTAL ) ? _size[0] - minorAxisLen : 0;
        farButtonPos[1] = ( _type == SCROLL_VERTICAL )   ? _size[1] - minorAxisLen : 0;

        add( _buttons[0], 0, 0 );
        add( _buttons[1], farButtonPos[0], farButtonPos[1] );
        
}

ScrollbarWidget::~ScrollbarWidget() {
        if ( _scrollViewport ) {
                _scrollViewport->removeWidgetEventListener( this );
                if ( _scrollViewport->getView() ) {
                        _scrollViewport->getView()->removeWidgetEventListener( this );
                }
        }
        delete _buttons[0];
        delete _buttons[1];
        delete _elevatorWidget;
}

void ScrollbarWidget::paint() {
        Widget::paint();
        
        glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
        
        glBegin( GL_QUADS );
        {
                glColor4fv( getLookAndFeel()->getSecondaryColor( COLOR_TWO ) );
        
                glVertex2f( 1,            1 );
                glVertex2f( _size[0] - 2, 1 );
                glVertex2f( _size[0] - 2, _size[1] - 2 );
                glVertex2f( 1,            _size[1] - 2 );

        }
        glEnd(); 
        glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );

        glBegin( GL_LINE_STRIP );
        {
                glColor4fv( getLookAndFeel()->getWhiteColor() );

                glVertex2f( 0,            0 );
                glVertex2f( _size[0] - 1, 0 );
                glVertex2f( _size[0] - 1, _size[1] - 1 );
                glVertex2f( _size[0] - 2, _size[1] - 1 );
                
                glColor4fv( getLookAndFeel()->getSecondaryColor( COLOR_ONE ) );
                
                glVertex2f( 0, _size[1] - 1 );
                glVertex2f( 0,            0 );
        }        
        glEnd();
}

/**
 * returns the number of pixels we slid (deprecated)
 */
int ScrollbarWidget::attemptSlide( int amount ) {
        
        //        int viewAmount = (int) ( _pixelRatio * amount );
        //        cout << " attemptSlide " << setw( 3 ) << amount << "; va: " << setw( 3 ) << viewAmount << endl;

        int newPos[2] = { _elevatorWidget->getPos()[0], _elevatorWidget->getPos()[1] };
        newPos[ _type ] += amount;
        newPos[ _type ] = min( max( _railMargin, newPos[ _type ] ), _railMargin + _railTravelLen );
        
        _elevatorWidget->setPosition( newPos[0], newPos[1] );
        
        setListenerEnabled( false );
        _scrollViewport->setViewPosition( _type, (float) ( _pixelRatio / 2 + (float) newPos[ _type ] - (float) _railMargin ) /
                                          _pixelRatio );
        setListenerEnabled( true );
        
        return 0;

}

bool ScrollbarWidget::mousePressed( SDL_MouseButtonEvent *evt ) {

        if ( Widget::mousePressed( evt ) )
                return true;

        int target_pos = ( _type == SCROLL_VERTICAL ) ? evt->y : evt->x;
        int cur_pos    = ( _type == SCROLL_VERTICAL ) ? _elevatorWidget->getPos()[1] : _elevatorWidget->getPos()[0];
        
        //
        // left button
        //
        
        if ( evt->button == 1 ) {
                _scrollViewport->scrollBlocks( _type, ( target_pos > cur_pos ) ? 1 : -1 );
        }

        //
        // middle button
        //
        if ( evt->button == 2 ) {
                //
                // warp the elevator so that the closest edge meets the event position
                //
                if ( target_pos < cur_pos )
                        attemptSlide( target_pos - cur_pos );
                else
                        attemptSlide( target_pos - cur_pos - _elevatorWidget->getLength() );
        }

        return true;
}

void ScrollbarWidget::handleEvent( const class WidgetEvent * evt ) {

        if ( evt->getType() == EVENT_VIEWPORT ) {
                class ViewportEvent *vp_evt = (ViewportEvent *) evt;
                
                if ( ! vp_evt->orientationChanged( _type ) )
                        return;

                int newElevatorPos[2] = {0, 0};
                newElevatorPos[ _type ] = 
                        (int) ( _pixelRatio * _scrollViewport->getViewPosition()[ _type ] ) + _railMargin;
                _elevatorWidget->setPosition( newElevatorPos[0], newElevatorPos[1] );
                return;
        }

        if ( evt->getType() == EVENT_RESIZE ) {

                int newsize[2] = { _elevatorWidget->getSize()[0],
                                   _elevatorWidget->getSize()[0] };

                newsize[ _type ] = getElevatorSize();
                
                _elevatorWidget->resize( newsize[0], newsize[1] );
                
                resetInternals();
                return;
        }
}

bool ScrollbarWidget::keyPressed( SDL_KeyboardEvent * evt, int x, int y ) {
        if ( evt->keysym.sym == SDLK_PAGEUP ) {
                _scrollViewport->scrollBlocks( _type, 1 );
                return true;
        }
        
        else if ( evt->keysym.sym == SDLK_PAGEDOWN ) {
                _scrollViewport->scrollBlocks( _type, -1 );
                return true;

        }

        return Widget::keyPressed( evt, x, y );
}
