/*
 * widgets/WidgetManager.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 "WidgetManager.h"
#include "ConsoleWidget.h"
#include "Draggable.h"

#include <SDL/SDL_thread.h>

class WidgetManager * WidgetManager::_instance = 0;

WidgetManager::WidgetManager( int x, int y )
        : _consoleEnabled( false ),
          _draggable( 0 ),
          _dndPickupStart( 0 ),
          _dndDragInProgress( 0 )
{
        Widget::setWidgetManager( this );
        _toplevelWidget = new Widget( x, y );
        _consoleWidget  = new ConsoleWidget( x, max( 50, y / 4 ) );
        _mutex = SDL_CreateMutex();
        _dndDropFailedSound = Mix_LoadWAV( "lib/sfx/dndFailed.wav" );

        const char * sdl_error = SDL_GetError( );
        
        if( *sdl_error ) {
                cout <<  " :: SDL error: WidgetManager::WidgetManager(): " << sdl_error << endl;
                SDL_ClearError();
        }
}

WidgetManager::~WidgetManager() {
        _draggable = 0;
        _dndPickupStart = 0;
        delete _consoleWidget;
        delete _toplevelWidget;
        _consoleWidget = 0;
        _toplevelWidget = 0;
        _mutex = 0;
        _dndDropFailedSound = 0;
        _consoleEnabled = false;
        _updateWidgets.clear();
        _postUpdateDeleteWidgets.clear();
}

class WidgetManager * WidgetManager::getInstance( int x, int y ) {
        return _instance ?  _instance : ( _instance = new WidgetManager( x, y ) );
}

void WidgetManager::add( class Widget * w, int x, int y ) {
        SDL_mutexP( _mutex );
        _toplevelWidget->add( w, x, y );
        SDL_mutexV( _mutex );
}

bool WidgetManager::removeWidget( class Widget * w ) {
        SDL_mutexP( _mutex );
        bool b = _toplevelWidget->removeWidget( w );
        SDL_mutexV( _mutex );
        return b;
}

void WidgetManager::display() {
        SDL_mutexP( _mutex );
        _toplevelWidget->display();
        SDL_mutexV( _mutex );
        //        if ( _consoleEnabled )
        //                _consoleWidget->display();
}

const int * WidgetManager::getSize() const { 
        SDL_mutexP( _mutex );
        const int * s = _toplevelWidget->getSize();
        SDL_mutexV( _mutex );
        return s;
}

bool WidgetManager::setConsoleEnabled( bool enabled ) {
        SDL_mutexP( _mutex );
        _consoleEnabled = enabled;

        if ( _consoleEnabled ) {
                _toplevelWidget->add( _consoleWidget, 
                                      0,
                                      _toplevelWidget->getSize()[1] - _consoleWidget->getSize()[1] );
        }
        else {
                _toplevelWidget->removeWidget( _consoleWidget );
        }

        SDL_mutexV( _mutex );
        
        return _consoleEnabled;
}

// class KeyListener
bool WidgetManager::keyPressed(    SDL_KeyboardEvent * evt, int x, int y ) {
        SDL_mutexP( _mutex );
        
        if (  evt->keysym.sym == SDLK_ESCAPE && _draggable ) {
                if ( attemptDNDDrop( x, y, true ) )
                        return true;
        }

        bool b = _toplevelWidget->keyPressed( evt, x, y );
        SDL_mutexV( _mutex );
        return b;
}
bool WidgetManager::keyReleased(   SDL_KeyboardEvent * evt ) {
        SDL_mutexP( _mutex );
        bool b = _toplevelWidget->keyReleased( evt );
        SDL_mutexV( _mutex );
        return b;
}
// class MouseButtonListener
bool WidgetManager::mousePressed(  SDL_MouseButtonEvent * evt ) {

        SDL_mutexP( _mutex );
        
        if ( _dndDragInProgress ) {
                SDL_mutexV( _mutex );
                return false;
        }

        if ( ! _toplevelWidget->mousePressed( evt ) ) {

                _dndPickupStart = true;
                _dndPickupPos[0] = evt->x;
                _dndPickupPos[1] = evt->y;

                SDL_mutexV( _mutex );

                return false;
        }
        
        SDL_mutexV( _mutex );

        return true;
        
}

bool WidgetManager::attemptDNDDrop( int x, int y, bool cancel ) {
        
        if ( _draggable == 0 )
                return false;
        
        class DNDGestureEvent * dnd_evt = 0;

        //
        // cancel current drag
        //

        if ( cancel && _dndPickupGesture ) {

                dnd_evt = _dndPickupGesture;
                dnd_evt->draggable = _draggable;
        }

        //
        // try to drop right here
        //

        else
                dnd_evt = new DNDGestureEvent( x, y, _draggable );
        
        class Draggable * new_draggable = 0;
        
        if ( _toplevelWidget->performDNDDrop( dnd_evt, &new_draggable ) ) {
                
                _draggable = 0;
                delete dnd_evt;

                // pickup forced
                if ( new_draggable ) {
                        _draggable = new_draggable;
                }
                
                // normal drop
                else {
                        _dndDragInProgress = false;
                        _dndPickupGesture = 0;
                }

                return true;
        }

        //
        // drop failed
        //

        Mix_PlayChannel( -1, _dndDropFailedSound, 0 );
        delete dnd_evt;
        return false;  
}
bool WidgetManager::mouseReleased( SDL_MouseButtonEvent * evt ) {
        
        SDL_mutexP( _mutex );
        _dndPickupStart = false;

        bool b;
        
        if ( _draggable ) {

                b = attemptDNDDrop( evt->x, evt->y, evt->button == 3 );
        }
        else {
                b = _toplevelWidget->mouseReleased( evt );
        }
        
        SDL_mutexV( _mutex );
        return b;
        
}
// class MouseMotionListener {
bool WidgetManager::mouseMoved(    SDL_MouseMotionEvent * evt ) {

        SDL_mutexP( _mutex );
        
        if ( ! _draggable &&
             _dndPickupStart &&
             ( abs( evt->x - _dndPickupPos[0] ) + abs( evt->y - _dndPickupPos[1] ) ) > 3 ) {
                        

                _dndPickupStart = false;

                class DNDGestureEvent * dnd_evt = new DNDGestureEvent( _dndPickupPos[0], 
                                                                       _dndPickupPos[1],
                                                                       _draggable );
                
                if ( ( _draggable = _toplevelWidget->performDNDPickup( dnd_evt ) ) ) {
                        _dndDragInProgress = true;
                        _dndPickupGesture = dnd_evt;
                        _dndPickupStart = false;

                        SDL_mutexV( _mutex );
        
                        return true;
                }
                
                delete dnd_evt;
        }

        bool b = _toplevelWidget->mouseMoved( evt );
        SDL_mutexV( _mutex );
        return b;
}
bool WidgetManager::mouseEntered(  SDL_MouseMotionEvent * evt ) {
        SDL_mutexP( _mutex );
        bool b =  _toplevelWidget->mouseEntered( evt );
        SDL_mutexV( _mutex );
        return b;
}
bool WidgetManager::mouseExited(   SDL_MouseMotionEvent * evt ) {
        SDL_mutexP( _mutex );
        bool b =  _toplevelWidget->mouseExited( evt );
        SDL_mutexV( _mutex );
        return b;
}

void WidgetManager::schedulePostUpdateDelete( class Widget * w ) {
}

bool WidgetManager::updateWidgets() {

        if ( _updateWidgets.size() ) {
                Vector<class Widget *>::iterator end = _updateWidgets.end();
                
                for ( Vector<class Widget *>::iterator iter = _updateWidgets.begin();
                      iter != end;
                      ++iter ) {
                        if ( (*iter)->isUpdateOk() ) {
                                (*iter)->updateWidget();
                        }
                }
                
                if ( _postUpdateDeleteWidgets.size() ) {
                        end = _postUpdateDeleteWidgets.end();
                        for ( Vector<class Widget *>::iterator iter = _postUpdateDeleteWidgets.begin();
                              iter != end;
                              ++iter ) {
                                removeUpdateWidget( *iter );
                                delete *iter;
                        }
                        _postUpdateDeleteWidgets.clear();
                }
                return true;
        }
        return false;
}

void WidgetManager::addUpdateWidget( class Widget * w ) {
        _updateWidgets.push_back( w );
}

void WidgetManager::removeUpdateWidget( class Widget * w ) {
        while ( _updateWidgets.remove( w ) ) {
        }
}
