/*
 * widgets/Widget.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 <iomanip>
#include <iostream>

#include "WidgetEvents.h"
#include "Widget.h"
#include "Viewport.h"
#include "WidgetManager.h"

#include "../util/TextureFont.h"

#include "../util/TextureManager.h"

class TextureFont * Widget::_font = 0;

class Widget * Widget::_inputControllingWidget = 0;

class LookAndFeel * Widget::_lookAndFeel = new LookAndFeel();

class WidgetManager * Widget::_wm = 0;

/**
 * constructor assist
 */
void Widget::initWidget( int width, int height ) {
        
        _updateOk = false;

        if ( _font == 0 ) {

                _font= new TextureFont( "font" );

                if ( _font->ok )
                        cout << " :: loaded widget font " << _font << endl;
                else
                        _font = 0;
        }

        _textureObject = 0;
        _textureCoords[0][0] = _textureCoords[0][1] = _textureCoords[3][0] = _textureCoords[1][1] = 0;
        _textureCoords[2][0] = _textureCoords[2][1] = _textureCoords[3][1] = _textureCoords[1][0] = 1;

        _viewport = 0;
        _event_loopback = false;

        _parent = 0;
        _lastWidget = 0;
        _inputControllingWidget = 0;
        _size[0] = width;
        _size[1] = height;

        _isFocused = false;
        _borderColor[0] = 0.3;
        _borderColor[1] = 0.3;
        _borderColor[2] = 0.3;
        _borderColor[3] = 1;

        _bgColor = 0;
        
        _fgColor = 0;
        /*
        _fgColor[0] = 1;
        _fgColor[1] = 1;
        _fgColor[2] = 1;
        _fgColor[3] = 1;
        */
        _marginColor[0] = 1;
        _marginColor[1] = 1;
        _marginColor[2] = 0;
        _marginColor[3] = 1;

        _name = "Widget";

        _extent[0] = 0;
        _extent[1] = 0;
        
        _ignoreEvents = false;

        _isOpaque = false;
        _drawBorder = true;
        _drawMargin = true;
        _needsToPaint = true;

        _pos[0] = 0;
        _pos[1] = 0;

        _absolutePos[0] = 0;
        _absolutePos[1] = 0;

        //        _prev = 0;
        //        _next = 0;
        //        _children = 0;

        _isEnabled = true;

}


/**
 * constructor
 */
Widget::Widget( int width, int height ) {
        initWidget( width, height );
}

/**
 * destructor
 */
Widget::~Widget() {

        unsetInputControllingWidget( this );

        //
        // remove self as view of viewport
        //

        if ( _viewport && _viewport->getView() == this )
                _viewport->setView( 0 );

        //
        // remove self from parent
        //

        if ( _parent )
                _parent->removeWidget( this );

        //
        // remove children from self
        //

        List<class Widget *>::iterator iter;
        while ( ( iter = _children.begin() ) != _children.end() )
                removeWidget( *iter );

        //
        // remove from widget manager's updatewidget list
        //

        WidgetManager::getInstance()->removeUpdateWidget( this );
}

void Widget::setBGTexture( const char * texname, GLenum type ) {
        if ( texname == 0 )
                _textureObject = 0;
        else
                _textureObject = TextureManager::getTexture( texname, type );
        
}

void Widget::setBGTextureCoords( float origin_x, 
                                 float origin_y, 
                                 float extent_x, 
                                 float extent_y ) {
        _textureCoords[0][0] = _textureCoords[3][0] = origin_x;
        _textureCoords[0][1] = _textureCoords[1][1] = origin_y;
        _textureCoords[1][0] = _textureCoords[2][0] = extent_x;
        _textureCoords[2][1] = _textureCoords[3][1] = extent_y;
}

const class Viewport * Widget::findViewport() const {
        const class Viewport * v = 0;
        const class Widget * w = this;
                
        while ( w ) {
                if ( ( v = w->getViewport() ) ) {
                        break;
                }
                w = w->getParent();
        }
                
        return v;
}

const class Viewport * Widget::findViewportClipRectangle( int clip[4] ) const {

        const class Viewport * v = findViewport();

        if ( ! v )
                return 0;
        
        const class Widget * w = v->getView();

        if ( ! w )
                return 0;

        clip[0] = w->getAbsolutePos()[0] - getAbsolutePos()[0] + v->getViewPosition()[0];
        clip[1] = w->getAbsolutePos()[1] - getAbsolutePos()[1] + v->getViewPosition()[1];

        clip[2] = v->getSize()[0];
        clip[3] = v->getSize()[1];

        return v;
}

void Widget::setViewport( class Viewport * v ) {
        _viewport = v;
}

void Widget::add( class Widget * w, int x, int y ) {
        
        if ( w->_parent ) {
                cout << " :: widget " << *w << " already has a parent." << endl;
                return;
        }

        w->_parent = this;

        _children.push_back( w );
        w->setPosition( x, y );
}

bool Widget::removeWidget( class Widget * w ) {

        bool b = _children.remove( w );

        if ( ! b ) {
                cout << " :: Widget.removeWidget() widget " << *w << " is not in " << *this << endl;
        }
        else {
                w->_parent = 0;
        }

        return b;
}

void Widget::setPosition( int x, int y ) {
        
        if ( ! _parent ) {
                cout << " :: Widget::setPosition() widget " << *this << " has no parent." << endl;
                return;
        }

        _pos[0] = x;
        _pos[1] = y;
        _absolutePos[0] = _parent->getAbsolutePos()[0] + x;
        _absolutePos[1] = _parent->getAbsolutePos()[1] + y;
        
        addAbsolutePos();
}

/**
 * descend through the children, fixing up _absolutePos as we go
 */
void Widget::addAbsolutePos() {

        _extent[0] = _pos[0] + _size[0];
        _extent[1] = _pos[1] + _size[1];

        list< class Widget * >::const_iterator end = _children.end();
        for ( list< class Widget *>::const_iterator iter = _children.begin();
              iter != end; ++iter ) {

                (*iter)->_absolutePos[0] = _absolutePos[0] + (*iter)->_pos[0];
                (*iter)->_absolutePos[1] = _absolutePos[1] + (*iter)->_pos[1];

                (*iter)->addAbsolutePos();
        }
}

void Widget::paint() {
        
        if ( _isOpaque ) {

                if ( isEnabled() )
                        glColor3fv( getBGColor4fv() );
                else
                        glColor3fv( _lookAndFeel->getSecondaryColor( COLOR_TWO ) );

                bool isTex = false;
                if ( _textureObject && _textureObject->getIndex() ) {
                        isTex = true;
                        
                        if ( _textureObject->isOk() ) {
                                glEnable( GL_TEXTURE_2D );
                                glBindTexture( GL_TEXTURE_2D, _textureObject->getIndex() );
                        }

                }
                
                glBegin( GL_QUADS );
                {
                        glTexCoord2fv( _textureCoords[0] );
                        glVertex2d( 0,            0 );
                        glTexCoord2fv( _textureCoords[1] );
                        glVertex2d( _size[0] - 1, 0 );
                        glTexCoord2fv( _textureCoords[2] );
                        glVertex2d( _size[0] - 1, _size[1] - 1 );
                        glTexCoord2fv( _textureCoords[3] );
                        glVertex2d( 0,            _size[1] - 1 );
                }
                glEnd();

                if ( isTex ) {
                        glDisable( GL_TEXTURE_2D );
                }
        }

        if ( _drawBorder ) {
                
                glColor3fv( getFGColor4fv() );
                
                glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );

                glBegin( GL_QUADS );
                {
                        glVertex2d( 0,            0 );
                        glVertex2d( _size[0] - 1, 0 );
                        glVertex2d( _size[0] - 1, _size[1] - 1 );
                        glVertex2d( 0,            _size[1] - 1 );
                }
                glEnd();

                glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );

        }
}

/**
 * assume translation has already occured for this widget
 */
void Widget::display() {

        glPushMatrix();
        {

                //
                // TODO: use _needsRedisplay to mark 'dirty' widgets for optimization
                //

                
                //
                // TODO: make sure the widget is even visible
                //

                //
                // translate if needed
                //
                
                if ( _pos[0] || _pos[1] )
                        glTranslatef( _pos[0], _pos[1], 0 );
                        
                paint();

                // optimization
                        
                if ( _children.size() ) {
                        
                        list< class Widget * >::const_iterator end = _children.end();
                        for ( list< class Widget *>::const_iterator iter = _children.begin();
                              iter != end; ++iter ) {

                                (*iter)->display();
                        }
                }
        }
        glPopMatrix();
}


void Widget::resize( int x, int y ) {
        
        int old_size[2] = { _size[0], _size[1] };

        _size[0] = x;
        _size[1] = y;

        notifyResizeListeners( this, old_size[0], old_size[1] );
}

// class KeyListener
bool Widget::keyPressed( SDL_KeyboardEvent * evt, int x, int y ) { 

        if ( _event_loopback ) {
                return false;
        }
        
        if ( _keyListeners.size() ) {
                Vector< class KeyListener * >::const_iterator end = _keyListeners.end();
                for ( Vector< class KeyListener *>::const_iterator iter = _keyListeners.begin();
                      iter != end; ++iter ) {
                        if ( (*iter)->keyPressed( evt, x, y ) )
                                return true;
                }
        }
        

        int p[2] = { x, y };
        
        if ( _inputControllingWidget ) {
                _event_loopback = true;
                x = p[0] - _inputControllingWidget->_absolutePos[0];
                y = p[1] - _inputControllingWidget->_absolutePos[1];
                _inputControllingWidget->keyPressed( evt, x, y );
                _event_loopback = false;
                return true;
        }

        class Widget *w = 0;

        list< class Widget * >::reverse_iterator end = _children.rend();
        for ( list< class Widget *>::reverse_iterator iter = _children.rbegin();
              iter != end; ++iter ) {

                w = *iter;

                if ( ! w->ignoreEvents() && w->isPointInside( p ) ) {
                        
                        x = p[0] - w->_pos[0];
                        y = p[1] - w->_pos[1];
                        
                        w->keyPressed( evt, x, y );

                        //                        x = p[0];
                        //                        y = p[1];

                        return true;
                }
        }

        return false;

}

bool Widget::keyReleased( SDL_KeyboardEvent * evt ) { 
        return false;
}

// class MouseButtonListener
bool Widget::mousePressed( SDL_MouseButtonEvent * evt ) { 

        if ( _event_loopback ) {
                return false;
        }
        
        if ( _mouseButtonListeners.size() ) {
                Vector< class MouseButtonListener * >::const_iterator end = 
                        _mouseButtonListeners.end();
                for ( Vector< class MouseButtonListener *>::const_iterator iter = 
                              _mouseButtonListeners.begin();
                      iter != end; ++iter ) {
                        if ( (*iter)->mousePressed( evt ) )
                                return true;
                }
        }

        int p[2] = { evt->x, evt->y };
        
        if ( _inputControllingWidget ) {
                _event_loopback = true;
                evt->x = p[0] - _inputControllingWidget->_absolutePos[0];
                evt->y = p[1] - _inputControllingWidget->_absolutePos[1];
                _inputControllingWidget->mousePressed( evt );
                _event_loopback = false;
                return true;
        }

        class Widget *w = 0;
        list< class Widget * >::reverse_iterator end = _children.rend();
        for ( list< class Widget *>::reverse_iterator iter = _children.rbegin();
              iter != end; ++iter ) {

                w = *iter;

                if ( ! w->ignoreEvents() && w->isPointInside( p ) ) {
                        
                        evt->x = p[0] - w->_pos[0];
                        evt->y = p[1] - w->_pos[1];

                        bool b = w->mousePressed( evt );

                        evt->x = p[0];
                        evt->y = p[1];

                        return b;
                }
        }

        return false;
}
bool Widget::mouseReleased( SDL_MouseButtonEvent * evt ) { 
        //        cout << "released " << *this << endl;
        
        if ( _event_loopback ) {
                return false;
        }

        int p[2] = { evt->x, evt->y };

        if ( _inputControllingWidget ) {
                _event_loopback = true;
                evt->x = p[0] - _inputControllingWidget->_absolutePos[0];
                evt->y = p[1] - _inputControllingWidget->_absolutePos[1];
                _inputControllingWidget->mouseReleased( evt );
                _event_loopback = false;
                return true;
        }

        class Widget *w = 0;

        list< class Widget * >::reverse_iterator end = _children.rend();
        for ( list< class Widget *>::reverse_iterator iter = _children.rbegin();
              iter != end; ++iter ) {

                w = *iter;
                if ( ! w->ignoreEvents() && w->isPointInside( p ) ) {
                        
                        evt->x = p[0] - w->_pos[0];
                        evt->y = p[1] - w->_pos[1];

                        bool b = w->mouseReleased( evt );

                        evt->x = p[0];
                        evt->y = p[1];

                        return b;
                }
        }

        return false; 
}

// class MouseMotionListener {
bool Widget::mouseMoved( SDL_MouseMotionEvent * evt ) { 

        if ( _event_loopback ) {
                return false;
        }

        if ( _inputControllingWidget ) {

                _event_loopback = true;

                evt->x -= _inputControllingWidget->_absolutePos[0];
                evt->y -= _inputControllingWidget->_absolutePos[1];

                _inputControllingWidget->mouseMoved( evt );
                _event_loopback = false;
                return true;
        }

        class Widget *w = 0;

        int p[2] = { evt->x, evt->y };

        bool found = false;
        list< class Widget * >::reverse_iterator end = _children.rend();
        for ( list< class Widget *>::reverse_iterator iter = _children.rbegin();
              iter != end; ++iter ) {

                w = *iter;
                if ( ! w->ignoreEvents() && w->isPointInside( p ) ) {

                        evt->x = p[0] - w->_pos[0];
                        evt->y = p[1] - w->_pos[1];
                        
                        w->mouseMoved( evt );

                        evt->x = p[0];
                        evt->y = p[1];

                        found = true;
                        break;
                }
                w = 0;
        }
        
        if ( w != _lastWidget ) {
                if ( w ) {
                        w->mouseEntered( evt );
                }
                if ( _lastWidget ) {
                        _lastWidget->mouseExited( evt );
                }
                _lastWidget = w;
        }
        return found;
}

bool Widget::mouseEntered( SDL_MouseMotionEvent * evt ) { 
        //        cout << " -- --  mouseentered " << *this << endl;
        _isFocused = true;
        return false;
}

bool Widget::mouseExited( SDL_MouseMotionEvent * evt ) { 

        //        cout << " -- -- mouseEXITED " << *this << endl;
        _isFocused = false;

        if ( _lastWidget ) {
                _lastWidget->mouseExited( evt );
                _lastWidget = 0;
        }
        return false;
}                     

bool Widget::isDNDDropOk(      DNDGestureEvent * evt ) {
        return true;
}

class Draggable * Widget::performDNDPickup( DNDGestureEvent * evt ) {
        
        if ( _event_loopback ) {
                return 0;
        }
        
        int p[2] = { evt->x, evt->y };
        
        if ( _inputControllingWidget ) {
                _event_loopback = true;
                evt->x = p[0] - _inputControllingWidget->_absolutePos[0];
                evt->y = p[1] - _inputControllingWidget->_absolutePos[1];
                class Draggable * d = _inputControllingWidget->performDNDPickup( evt );
                _event_loopback = false;
                return d;
        }

        class Widget *w = 0;
        list< class Widget * >::reverse_iterator end = _children.rend();
        for ( list< class Widget *>::reverse_iterator iter = _children.rbegin();
              iter != end; ++iter ) {

                w = *iter;
                if ( ! w->ignoreEvents() && w->isPointInside( p ) ) {
                        
                        evt->x = p[0] - w->_pos[0];
                        evt->y = p[1] - w->_pos[1];

                        class Draggable * d = w->performDNDPickup( evt );

                        evt->x = p[0];
                        evt->y = p[1];

                        return d;
                }
        }

        return 0;
}

bool Widget::performDNDDrop(   DNDGestureEvent * evt, class Draggable ** d ) { 

        if ( _event_loopback ) {
                return false;
        }
        
        int p[2] = { evt->x, evt->y };

        if ( _inputControllingWidget ) {
                _event_loopback = true;
                evt->x = p[0] - _inputControllingWidget->_absolutePos[0];
                evt->y = p[1] - _inputControllingWidget->_absolutePos[1];
                bool b = _inputControllingWidget->performDNDDrop( evt, d );
                _event_loopback = false;
                return b;
        }

        class Widget *w = 0;
        list< class Widget * >::reverse_iterator end = _children.rend();
        for ( list< class Widget *>::reverse_iterator iter = _children.rbegin();
              iter != end; ++iter ) {

                w = *iter;
                if ( ! w->ignoreEvents() && w->isPointInside( p ) ) {
                        
                        evt->x = p[0] - w->_pos[0];
                        evt->y = p[1] - w->_pos[1];

                        bool b = w->performDNDDrop( evt, d );

                        evt->x = p[0];
                        evt->y = p[1];

                        return b;
                }
        }

        return false;
}

void Widget::notifyWidgetEventListeners( class WidgetEvent * evt ) {
        Vector<class WidgetEventListener *>::const_iterator end = _widgetEventListeners.end();

        class WidgetEventListener *listener = 0;

        for ( Vector<class WidgetEventListener *>::const_iterator iter = _widgetEventListeners.begin();
              iter != end; ++iter ) {
                listener = *iter;
                
                if ( listener->eventTypeOk( evt->getType() ) ) {
                        listener->handleEvent( evt );
                }
        }
}

void Widget::notifyActionListeners( class Widget * source, const char * command ) {
        class ActionEvent evt( source, command );
        notifyWidgetEventListeners( &evt );
        //        delete evt;
}

void Widget::notifyViewportListeners( class Viewport * source, int old_x, int old_y ) {
        class ViewportEvent * evt = new ViewportEvent( source, 
                                                       old_x, old_y,
                                                       source->getViewPosition()[0],
                                                       source->getViewPosition()[1] );
        notifyWidgetEventListeners( evt );
        delete evt;
}

void Widget::notifyResizeListeners( class Widget * source, int old_x, int old_y ) {
        class ResizeEvent * evt = new ResizeEvent( source, old_x, old_y );
        notifyWidgetEventListeners( evt );
        delete evt;
}
 
void Widget::addWidgetEventListener( class WidgetEventListener * listener ) {
        _widgetEventListeners.push_back( listener );
}

void Widget::removeWidgetEventListener( class WidgetEventListener * listener ) {
        _widgetEventListeners.remove( listener );
}

void Widget::addKeyListener(    class KeyListener * listener ) {
        _keyListeners.push_back( listener );
}
void Widget::removeKeyListener( class KeyListener * listener ) {
        _keyListeners.remove( listener );
}
        
void Widget::addMouseButtonListener(    class MouseButtonListener * listener ) {
        _mouseButtonListeners.push_back( listener );
}
void Widget::removeMouseButtonListener( class MouseButtonListener * listener ) {
        _mouseButtonListeners.remove( listener );
}

void Widget::addMouseMotionListener(    class MouseMotionListener * listener ) {
        _mouseMotionListeners.push_back( listener );
}
void Widget::removeMouseMotionListener( class MouseMotionListener * listener ) {
        _mouseMotionListeners.remove( listener );
}


void Widget::setInputControllingWidget( class Widget * w ) {
        if ( w != 0 && _inputControllingWidget == 0 ) {
                _inputControllingWidget = w;
        }
        else {
                cout << "error in setInputControllingWidget" << endl;
        }
}

void Widget::unsetInputControllingWidget( class Widget * w ) {
        if ( _inputControllingWidget == w )
                _inputControllingWidget = 0;
}

ostream& operator<<( ostream &os, const class Widget& w ) {
        os << w._name << " : " << setw(10) << &w << " [" << setw(3) << w._size[0] << "x" << setw(3) << w._size[1] << "]";
        return os;
}

