/***************************************************************************
 *   Copyright (c) 2005 Werner Mayer <wmayer[at]users.sourceforge.net>     *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Library General Public           *
 *   License as published by the Free Software Foundation; either          *
 *   version 2 of the License, or (at your option) any later version.      *
 *                                                                         *
 *   This library  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 Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this library; see the file COPYING.LIB. If not,    *
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
 *   Suite 330, Boston, MA  02111-1307, USA                                *
 *                                                                         *
 ***************************************************************************/


#include "PreCompiled.h"

#ifndef _PreComp_
# include <qapplication.h>
# include <qevent.h>
# include <qpainter.h>
# include <qpixmap.h>
# include <QMenu>
# include <Inventor/SbBox.h>
# include <Inventor/events/SoEvent.h>
# include <Inventor/events/SoKeyboardEvent.h>
# include <Inventor/events/SoLocation2Event.h>
# include <Inventor/events/SoMouseButtonEvent.h>
#endif

#include <Base/Console.h>

#include "MouseSelection.h"
#include "View3DInventor.h"
#include "View3DInventorViewer.h"

using namespace Gui; 

AbstractMouseSelection::AbstractMouseSelection() : _pcView3D(0)
{
    m_bInner = true;
    mustRedraw = false;
}

void AbstractMouseSelection::grabMouseModel( Gui::View3DInventorViewer* viewer )
{
    _pcView3D=viewer;
    m_cPrevCursor = _pcView3D->getWidget()->cursor();

    // do initialization of your mousemodel
    initialize();
}

void AbstractMouseSelection::releaseMouseModel()
{
    // do termination of your mousemodel
    terminate();

    _pcView3D->getWidget()->setCursor(m_cPrevCursor);
    _pcView3D = 0;
}

void AbstractMouseSelection::redraw()
{
    // Note: For any reason it does not work to do a redraw in the actualRedraw() method of the
    // viewer class. So, we do the redraw when the user continues moving the cursor. E.g. have
    // a look to PolyPickerSelection::draw()
    mustRedraw = true;
}

int AbstractMouseSelection::handleEvent(const SoEvent * const ev, const SbViewportRegion& vp)
{
    int ret=Continue;

    const SbVec2s& sz = vp.getWindowSize(); 
    short w,h; sz.getValue(w,h);

    SbVec2s loc = ev->getPosition();
    short x,y; loc.getValue(x,y);
    y = h-y; // the origin is at the left bottom corner (instead of left top corner)

    if (ev->getTypeId().isDerivedFrom(SoMouseButtonEvent::getClassTypeId())) {
        const SoMouseButtonEvent * const event = (const SoMouseButtonEvent *) ev;
        const SbBool press = event->getState() == SoButtonEvent::DOWN ? TRUE : FALSE;

        if (press) {
            _clPoly.push_back(ev->getPosition());
            ret = mouseButtonEvent(static_cast<const SoMouseButtonEvent*>(ev), QPoint(x,y));
        }
        else {
            ret = mouseButtonEvent(static_cast<const SoMouseButtonEvent*>(ev), QPoint(x,y));
        }
    }
    else if (ev->getTypeId().isDerivedFrom(SoLocation2Event::getClassTypeId())) {
        ret = locationEvent(static_cast<const SoLocation2Event*>(ev), QPoint(x,y));
    }
    else if (ev->getTypeId().isDerivedFrom(SoKeyboardEvent::getClassTypeId())) {
        ret = keyboardEvent(static_cast<const SoKeyboardEvent*>(ev));
    }

    if (ret == Restart)
        _clPoly.clear();

    return ret;
}

// -----------------------------------------------------------------------------------

BaseMouseSelection::BaseMouseSelection()
  : AbstractMouseSelection()
{
}

// -----------------------------------------------------------------------------------
#if 0
/* XPM */
static const char *cursor_polypick[]={
"32 32 2 1",
"# c #646464",
". c None",
"................................",
"................................",
".......#........................",
".......#........................",
".......#........................",
"................................",
".......#........................",
"..###.###.###...................",
".......#...............#........",
"......................##........",
".......#..............#.#.......",
".......#.............#..#.......",
".......#............#...#.......",
"....................#....#......",
"...................#.....#......",
"..................#......#......",
"............#.....#.......#.....",
"...........#.##..#........#.....",
"..........#....##.........#.....",
".........#...............#......",
"........#................#......",
".......#................#.......",
"......#.................#.......",
".....#.................#........",
"....#####..............#........",
".........#########....#.........",
"..................#####.........",
"................................",
"................................",
"................................",
"................................",
"................................"};

/* XPM */
static const char *cursor_scissors[]={
"32 32 3 1",
"# c #000000",
"+ c #ffffff",
". c None",
"....+...........................",
"....+...........................",
"....+...........................",
"................................",
"+++.+.+++.......................",
"................................",
"....+...........................",
"....+...................#####...",
"....+.................########..",
".....................#########..",
".....###............##########..",
"....##++##.........#####...###..",
"...#++++++##.......####...####..",
"...##+++++++#......####.######..",
".....#+++++++##....##########...",
"......##+++++++##.##########....",
"........##+++++++#########......",
"..........#+++++++#####.........",
"...........##+++++####..........",
"...........##+++++###...........",
".........##+++++++########......",
"........##+++++++###########....",
"......##+++++++##.###########...",
"....##+++++++##....##########...",
"...#+++++++##......####..#####..",
"...#++++++#........#####..####..",
"....##++##..........#####..###..",
"......#.............##########..",
".....................#########..",
".......................######...",
"................................",
"................................"};
#endif
static const char *cursor_cut_scissors[]={
"32 32 6 1",
"a c #800000",
"c c #808080",
"+ c #c0c0c0",
"b c #ff0000",
"# c #ffffff",
". c None",
"....#...........................",
"....#...........................",
"....#...........................",
"................................",
"###.#.###.......................",
"................................",
"....#...........................",
"....#...................aaaaa...",
"....#.................aabbbbba..",
".....................abbbbbbba..",
".....ccc............abbaaaaabb..",
"....cc++cc.........babaa...aba..",
"...c+#++++cc.......abba...abba..",
"...cc+#+++++c......abba.aabbaa..",
".....c+++++#+cc....abbaaabbaa...",
"......cc+#+++#+cc.aabbbbbbaa....",
"........cc+#+++#+cabbbaaaa......",
"..........c+++++++abbaa.........",
"...........cc+++#+aaaa..........",
"...........cc+#+++caa...........",
".........cc+++++#+cbbaaaaa......",
"........cc+#+++#+cabbabbbaaa....",
"......cc+#+++#+cc.aaabbbbbbaa...",
"....cc+#+++#+cc....abbaaaabba...",
"...c++#++#+cc......abba..aabba..",
"...c+###++c........aabaa..aaba..",
"....cc++cc..........abbaa..aba..",
"......c.............aabbaaaaba..",
".....................baabbbbba..",
".......................aaaaaa...",
"................................",
"................................"};

PolyPickerSelection::PolyPickerSelection() 
{
    m_iRadius    = 2;
    m_iNodes     = 0;
    m_bWorking   = false;
}

void PolyPickerSelection::initialize()
{
    QPixmap p(cursor_cut_scissors);
    QCursor cursor(p, 4, 4);
    _pcView3D->getWidget()->setCursor(cursor);
}

void PolyPickerSelection::terminate()
{
//  _pcView3D->getGLWidget()->releaseMouse();
}

void PolyPickerSelection::draw ()
{
    if (mustRedraw){
        if (_cNodeVector.size() > 1) {
            QPoint start = _cNodeVector.front();
            for (std::vector<QPoint>::iterator it = _cNodeVector.begin()+1; it != _cNodeVector.end(); ++it) {
                _pcView3D->drawLine(start.x(),start.y(),it->x(), it->y() );
                start = *it;
            }
        }

        // recursive call, but no infinite loop
        mustRedraw = false;
        draw();
    }
    if (m_bWorking) {
        if (m_iNodes < int(_cNodeVector.size())) {
            m_iNodes = int(_cNodeVector.size());

            if (_cNodeVector.size() > 2) {
                QPoint start = _cNodeVector.front();
                _pcView3D->drawLine(m_iXnew,m_iYnew,start.x(), start.y() );
            }
        }
        else {
            _pcView3D->drawLine(m_iXnew,m_iYnew,m_iXold,m_iYold);
            if (_cNodeVector.size() > 1) {
                QPoint start = _cNodeVector.front();
                _pcView3D->drawLine(m_iXnew,m_iYnew,start.x(), start.y());
            }
        }
    }
}

PolyPickerSelection::~PolyPickerSelection()
{
}

int PolyPickerSelection::popupMenu()
{
    QMenu menu;
    QAction* fi = menu.addAction(QObject::tr("Finish"));
    menu.addAction(QObject::tr("Clear"));
    QAction* ca = menu.addAction(QObject::tr("Cancel"));
    if (getPositions().size() < 3)
        fi->setEnabled(false);
    QAction* id = menu.exec(QCursor::pos());
    if (id == fi)
        return Finish;
    else if (id == ca)
        return Cancel;
    else
        return Restart;
}

int PolyPickerSelection::mouseButtonEvent( const SoMouseButtonEvent * const e, const QPoint& pos )
{
    const int button = e->getButton();
    const SbBool press = e->getState() == SoButtonEvent::DOWN ? TRUE : FALSE;

    if (press) {
        switch (button)
        {
        case SoMouseButtonEvent::BUTTON1:
            {
                // start working from now on
                if (!m_bWorking) {
                    m_bWorking = true;
                    // clear the old polygon
                    _cNodeVector.clear();
                    _pcView3D->getGLWidget()->update();
//                  _pcView3D->getGLWidget()->grabMouse();
                }

                _cNodeVector.push_back(pos);

                m_iXnew = pos.x();  m_iYnew = pos.y();
                m_iXold = pos.x();  m_iYold = pos.y();
            }   break;
        case SoMouseButtonEvent::BUTTON2:
            {
                if (_cNodeVector.size() > 0) {
                    if (_cNodeVector.back() != pos)
                        _cNodeVector.push_back(pos);
                    m_iXnew = pos.x();  m_iYnew = pos.y();
                    m_iXold = pos.x();  m_iYold = pos.y();
                }

                QCursor cur = _pcView3D->getWidget()->cursor();
                _pcView3D->getWidget()->setCursor(m_cPrevCursor);
//              _pcView3D->getGLWidget()->releaseMouse();

                int id = popupMenu();
                if (id == Finish || id == Cancel) {
                    releaseMouseModel();
                }
                else if (id == Restart) {
                    m_bWorking = false;
                    m_iNodes = 0;
                    _pcView3D->getWidget()->setCursor(cur);
                }
                return id;
            }   break;
        default:
            {
            }   break;
        }
    }

    return Continue;
}

int PolyPickerSelection::locationEvent( const SoLocation2Event * const e, const QPoint& pos )
{
    // do all the drawing stuff for us
    QPoint clPoint = pos;

    if (m_bWorking) {
        // check the position
        QRect r = _pcView3D->getGLWidget()->rect();
        if (!r.contains(clPoint)) {
            if (clPoint.x() < r.left())
                clPoint.setX( r.left());
            if (clPoint.x() > r.right())
                clPoint.setX(r.right());
            if (clPoint.y() < r.top())
                clPoint.setY(r.top());
            if (clPoint.y() > r.bottom())
                clPoint.setY(r.bottom());

#ifdef FC_OS_WINDOWS
            QPoint newPos = _pcView3D->getGLWidget()->mapToGlobal(clPoint);
            QCursor::setPos(newPos);
#endif
        }
    }

    draw();
    m_iXnew = clPoint.x();
    m_iYnew = clPoint.y();
    draw();

    return Continue;
}

int PolyPickerSelection::keyboardEvent( const SoKeyboardEvent * const e )
{
    return Continue;
}

// -----------------------------------------------------------------------------------

PolyClipSelection::PolyClipSelection() 
{
}

PolyClipSelection::~PolyClipSelection()
{
}

int PolyClipSelection::popupMenu()
{
    QMenu menu;
    QAction* ci = menu.addAction(QObject::tr("Inner"));
    QAction* co = menu.addAction(QObject::tr("Outer"));
    QAction* ca = menu.addAction(QObject::tr("Cancel"));
    if (getPositions().size() < 3) {
        ci->setEnabled(false);
        co->setEnabled(false);
    }
    QAction* id = menu.exec(QCursor::pos());
    if (id == ci) {
        m_bInner = true;
        return Finish;
    }
    else if (id == co) {
        m_bInner = false;
        return Finish;
    }
    else if (id == ca)
        return Cancel;
    else
        return Restart;
}

// -----------------------------------------------------------------------------------

RectangleSelection::RectangleSelection()
{
    m_bWorking = false;
}

RectangleSelection::~RectangleSelection()
{
}

void RectangleSelection::initialize()
{
}

void RectangleSelection::terminate()
{
}

void RectangleSelection::draw ()
{
    if (m_bWorking)
        _pcView3D->drawRect(m_iXold, m_iYold, m_iXnew, m_iYnew);
}

int RectangleSelection::mouseButtonEvent( const SoMouseButtonEvent * const e, const QPoint& pos )
{
    const int button = e->getButton();
    const SbBool press = e->getState() == SoButtonEvent::DOWN ? TRUE : FALSE;

    int ret = Continue;

    if (press) {
        switch ( button )
        {
        case SoMouseButtonEvent::BUTTON1:
            {
                m_bWorking = true;
                m_iXold = m_iXnew = pos.x(); 
                m_iYold = m_iYnew = pos.y();
            }   break;
        default:
            {
            }   break;
        }
    }
    else {
        switch (button) {
            case SoMouseButtonEvent::BUTTON1:
                {
                    releaseMouseModel();
                    m_bWorking = false;
                    ret = Finish;
                }   break;
            default:
                {
                }   break;
        }
    }

    return ret;
}

int RectangleSelection::locationEvent( const SoLocation2Event * const e, const QPoint& pos )
{
    draw();
    m_iXnew = pos.x(); 
    m_iYnew = pos.y();
    draw();
    return Continue;
}

int RectangleSelection::keyboardEvent( const SoKeyboardEvent * const e )
{
    return Continue;
}

// -----------------------------------------------------------------------------------

BoxZoomSelection::BoxZoomSelection()
{
}

BoxZoomSelection::~BoxZoomSelection()
{
}

void BoxZoomSelection::terminate()
{
    int xmin = std::min<int>(m_iXold, m_iXnew);
    int xmax = std::max<int>(m_iXold, m_iXnew);
    int ymin = std::min<int>(m_iYold, m_iYnew);
    int ymax = std::max<int>(m_iYold, m_iYnew);
    SbBox2s box(xmin, ymin, xmax, ymax);
    _pcView3D->boxZoom(box);
}
