#include "controller.h"
#include "arbeitsview.h"
#include "mainwindow.h"
#include "kmath.h"
#include <QRubberBand>
#include <QDebug>

ArbeitsController *ArbeitsController::controller[ USERMODE_COUNT ];

// gibt den Controller zurück, welcher in einem Bestimmten Modus verwendet
// wird
//  m: der Modus, bei dem der gesuchte Controller benutzt wird
ArbeitsController *ArbeitsController::getController( UserMode m )
{
    return controller[ m ];
}

// Erstellt die Controller, welche bei den verschiedenen Modis benutzt
// werden sollen
//  m: Das Modell, welches alle nötigen Daten vür die Arbeitsansicht enthält
void ArbeitsController::initStaticController( ArbeitsModel *m )
{
    controller[ SELECT ] = new HideController( m );
    controller[ MOVE ] = new MoveController( m );
    controller[ NEW ] = new NewController( m );
    controller[ PIPETTE_SELECT ] = new CopyController( m );
    controller[ PIPETTE_SET ] = new PasteController( m );
    controller[ DELETE ] = new DeleteController( m );
    controller[ CUT ] = new CutController( m );
    controller[ ZOOM_IN ] = new ZoomInController( m );
}

ArbeitsController::ArbeitsController( ArbeitsModel *m )
{
    model = m;
}

// Setzt den Controller zurück
void ArbeitsController::restartController()
{
    model->notifyViews();
}

// verwaltet das drehen des Mausrades
void ArbeitsController::wheelEvent(QWheelEvent *e)
{
    if( !model->getFrame() )
        return;
    // Ermitteln der View Größe und der Bildgröße
    QSize s = model->getViewSize();
    QRect imgRect( QPoint( 0, 0 ), s );
    QSize imgS = model->getImage().size();
    if( imgS.width() > s.width() )
    {
        imgS.scale( s, Qt::KeepAspectRatio );
        imgRect.setSize( imgS );
    }
    QPoint bottomRight = model->inverseTranslate( imgRect.bottomRight() );
    int xOffset = model->getXOffset(); // Aktualisieren des aus dem Bild angezeigten gebietes
    int yOffset = model->getYOffset();
    xOffset += (int)( e->angleDelta().y() * (imgRect.width() / 500.0) ) / 2;
    yOffset += (int)( e->angleDelta().y() * (imgRect.height() / 500.0) ) / 2;
    QPoint nBottomRight( bottomRight.x() - (int)( e->angleDelta().y() * (imgRect.width() / 500.0) ) / 2,
                         bottomRight.y() - (int)( e->angleDelta().y() * (imgRect.height() / 500.0) ) / 2 );
    if( e->angleDelta().y() > 0 )
    {
        QPoint center( model->getViewPos().x() + model->getViewSize().width() / 2, model->getViewPos().y() + model->getViewSize().height() / 2 );
        QPoint move = e->pos() - center;
        xOffset += move.x();
        yOffset += move.y();
        nBottomRight.setX( nBottomRight.x() + move.x() );
        nBottomRight.setY( nBottomRight.y() + move.y() );
    }
    if( xOffset < 0 )
    {
        nBottomRight.setX( nBottomRight.x() - xOffset );
        xOffset -= xOffset;
    }
    if( yOffset < 0 )
    {
        nBottomRight.setY( nBottomRight.y() - yOffset );
        yOffset -= yOffset;
    }
    if( nBottomRight.x() > model->getImage().width() )
    {
        xOffset -= nBottomRight.x() - model->getImage().width();
        nBottomRight.setX( model->getImage().width() );
    }
    if( nBottomRight.y() > model->getImage().height() )
    {
        yOffset -= nBottomRight.y() - model->getImage().height();
        nBottomRight.setY( model->getImage().height() );
    }
    if( xOffset >= nBottomRight.x() || yOffset >= nBottomRight.y() )
        return; // abbruch
    // Aktualisieren der Sklallierungsfaktoren
    if( xOffset < 0 || yOffset < 0 )
    {
        xOffset = 0;
        yOffset = 0;
        model->setScaleFactor(imgRect.width() / (float)model->getImage().width(), imgRect.height() / (float)model->getImage().height());
        model->notifyViews();
        return;
    }
    model->setScaleFactor( imgRect.width() / (float)(nBottomRight.x() - xOffset), imgRect.height() / (float)(nBottomRight.y() - yOffset));
    model->setOffset( xOffset, yOffset );
    model->notifyViews();
}

// verwaltet das Drücken einer Maustaste
void ArbeitsController::mousePressEvent( QMouseEvent *e )
{
    model->setMouseButtonPressed( e->button(), e->pos() );
    model->notifyViews();
}

// verwaltet das Bewegen der Maus
void ArbeitsController::mouseMoveEvent( QMouseEvent *e )
{
    if( model->isMouseButtonPressed( Qt::MidButton ) )
    { // Falls die mittlere Masutaste gedrückt ist, kann der nutzer den angezeigten Bereich des Bildes verschieben
        QPoint diff = e->pos() - model->getMousePoint();
        diff = QPoint( (int)(diff.x() / model->getXScaleFactor()) * 5, (int)(diff.y() / model->getYScaleFactor()) * 5 );
        if( diff != QPoint( 0, 0 ) )
        {
            QSize s = model->getViewSize();
            QRect imgRect( QPoint( 0, 0 ), s );
            QSize imgS = model->getImage().size();
            if( imgS.width() > s.width() )
            {
                imgS.scale( s, Qt::KeepAspectRatio );
                imgRect.setSize( imgS );
            }
            QPoint bottomRight = model->inverseTranslate( imgRect.bottomRight() );
            int xOffset = model->getXOffset(), yOffset = model->getYOffset();
            bottomRight.setX( bottomRight.x() + diff.x() );
            bottomRight.setY( bottomRight.y() + diff.y() );
            xOffset += diff.x();
            yOffset += diff.y();
            if( xOffset < 0 )
            {
                bottomRight.setX( bottomRight.x() - xOffset );
                xOffset -= xOffset;
            }
            if( yOffset < 0 )
            {
                bottomRight.setY( bottomRight.y() - yOffset );
                yOffset -= yOffset;
            }
            if( bottomRight.x() > model->getImage().width() )
            {
                xOffset -= bottomRight.x() - model->getImage().width();
                bottomRight.setX( model->getImage().width() );
            }
            if( bottomRight.y() > model->getImage().height() )
            {
                yOffset -= bottomRight.y() - model->getImage().height();
                bottomRight.setY( model->getImage().height() );
            }
            if( xOffset >= bottomRight.x() || yOffset >= bottomRight.y() )
                return;
            model->setOffset( xOffset, yOffset );
            model->notifyViews();
        }
    }
    model->setMousePoint( e->pos() );
    model->notifyViews();
}

// verwaltet das Loslassen einer Maustaste
void ArbeitsController::mouseReleaseEvent( QMouseEvent *e )
{
    model->setMousePressed( false );
    model->notifyViews();
}


/*
 * Controller für den SELECT Modus
 */

HideController::HideController( ArbeitsModel *m )
    : ArbeitsController( m )
{}

void HideController::mouseReleaseEvent( QMouseEvent *e )
{ // Verstecken oder sichtbarmachen des Objektes, auf das geklickt wurde
    if( model->isMouseButtonPressed( Qt::LeftButton ) && e )
    {
        model->getFrame()->selectObjectAt( model->inverseTranslate( e->pos() ) );
        model->getWindow()->setFrameTreeSelection();
    }
    ArbeitsController::mouseReleaseEvent( e );
}

/*
 * Controller für den MOVE Modus
 */

MoveController::MoveController( ArbeitsModel *m )
    : ArbeitsController( m )
{}

void MoveController::mousePressEvent( QMouseEvent *e )
{ // Auswahl des Punktes der verschoben werden soll
    if( e->button() == Qt::LeftButton && !model->getMoveObject().isNull() && model->getInsertIndex() >= 0 )
    {
        model->getMoveObject()->insertVertex( model->getInsertIndex(), model->getNewVertex(), model->getMovePolygon() );
        model->setMoveIndex( model->getInsertIndex() );
        model->setInsertIndex( -1 );
    }
    ArbeitsController::mousePressEvent( e );
}

void MoveController::mouseMoveEvent( QMouseEvent *e )
{ // Verschieben des ausgewählten Punktes und findes des der Maus nächstgelegenen Punktes
    if( e )
    {
        if( !model->isMousePressed() )
        { // findes des der Maus nächstgelegenen Punktes
            bool foundNp = false;
            QPoint mousePos = model->inverseTranslate(e->pos());
            double mdiff = 0;
            for( ObjectPolygon obj : model->getFrame()->getObjects() )
            {
                if( obj->isSelected() )
                {
                    int pIndex = 0;
                    for( QPolygon o : obj->getPolygonList() )
                    {
                        QPoint last = o.last();
                        int index = 0;
                        for( QPoint point : o )
                        {
                            QPoint tmp = Math::getNearestPointOnLine( mousePos, point, last );
                            QPoint dir = mousePos - tmp;
                            int tmpDiff = dir.x() * dir.x() + dir.y() * dir.y();
                            if( !foundNp || tmpDiff < mdiff )
                            {
                                if( Math::diffSquare( tmp, point ) < 500 )
                                {
                                    model->setMoveObject( obj, pIndex );
                                    model->setMoveIndex( index );
                                    model->setInsertIndex( -1 );
                                }
                                else if( Math::diffSquare( tmp, last ) < 500 )
                                {
                                    model->setMoveObject( obj, pIndex );
                                    model->setMoveIndex( index - 1 );
                                    model->setInsertIndex( -1 );
                                    if( index - 1 < 0 )
                                        model->setMoveIndex( o.count() - 1 );
                                }
                                else
                                {
                                    model->setMoveObject( obj, pIndex );
                                    model->setInsertIndex( index );
                                    model->setMoveIndex( -1 );
                                    model->setNewVertex( tmp );
                                }
                                foundNp = true;
                                mdiff = tmpDiff;
                            }
                            last = point;
                            index++;
                        }
                        pIndex++;
                    }
                }
            }
            if( !foundNp || mdiff > 500 )
            {
                model->setInsertIndex( -1 );
                model->setMoveIndex( -1 );
            }
        }
        // move
        if( model->isMouseButtonPressed( Qt::LeftButton ) && model->getMoveIndex() >= 0 && !model->getMoveObject().isNull() )
        {
            model->getMoveObject()->moveVertex( model->getMoveIndex(), model->inverseTranslate( e->pos() ), model->getImage().size(), model->getMovePolygon() );
        }
    }
    ArbeitsController::mouseMoveEvent( e );
}

/*
 * Controller für den CUT Modus
 */

CutController::CutController( ArbeitsModel *m )
    : ArbeitsController( m )
{}

void CutController::mouseReleaseEvent( QMouseEvent *e )
{
    if( model->isMouseButtonPressed( Qt::LeftButton ) && e )
    { // Auswahl der Punkte an denen das Objekt zerschnitten werden soll
        QPoint pos = model->inverseTranslate( e->pos() );
        QList< ObjectPolygon > objects = model->getFrame()->getObjects();
        if( model->getCutObject().isNull() )
        { // Auswahl des ersten Punktes
            for( auto p = objects.begin(); p != objects.end(); p++ )
            {
                int pIndex = 0;
                for( QPolygon pol : (*p)->getPolygonList() )
                {
                    int index = 0;
                    for( QPoint point : pol )
                    {
                        if( pos.x() > point.x() - 10 && pos.x() < point.x() + 10 && pos.y() > point.y() - 10 && pos.y() < point.y() + 10 )
                        {
                            model->setCutObject( *p, pIndex );
                            model->setCutIndex( index );
                        }
                        index++;
                    }
                    if( !model->getCutObject().isNull() )
                        break;
                    pIndex++;
                }
            }
        }
        else
        { // Auswahl des zweiten Punktes
            if( !model->getCutObject().isNull() && model->getFrame() )
            {
                int index = 0;
                foreach( QPoint point, model->getCutObject()->getPolygonList().at( model->getCutPolygon() ) )
                {
                    if( pos.x() > point.x() - 10 && pos.x() < point.x() + 10 && pos.y() > point.y() - 10 && pos.y() < point.y() + 10 )
                        break;
                    index++;
                }
                if( index >= model->getCutIndex() - 1 && index <= model->getCutIndex() + 1 )
                    return;
                if( ( model->getCutIndex() == 0 && index == model->getCutObject()->getPolygonList().at( model->getCutPolygon() ).size() - 1 ) ||
                        ( index == 0 && model->getCutIndex() == model->getCutObject()->getPolygonList().at( model->getCutPolygon() ).size() - 1 ) ||
                        index == model->getCutObject()->getPolygonList().at( model->getCutPolygon() ).size() )
                    return;
                // Zerschneiden des Objektes an den zwei eckpunkten
                model->getFrame()->splitObject( model->getCutObject(), model->getCutIndex(), index, model->getCutPolygon() );
                model->setCutObject( ObjectPolygon(), 0 );
                model->setCutIndex( -1 );
                model->getWindow()->setupFrameTree();
            }
        }
    }
    ArbeitsController::mouseReleaseEvent( e );
}


/*
 * Controller für den PIPETTE_SELECT Modus
 */

CopyController::CopyController( ArbeitsModel *m )
    : ArbeitsController( m )
{}

void CopyController::mouseReleaseEvent( QMouseEvent *e )
{ // Auswahl des Objektes, welches kopiert werden soll
    if( model->isMouseButtonPressed( Qt::LeftButton ) && e )
    {
        if( model->getFrame() )
        {
            int pIndex;
            model->setCopyedObject( model->getFrame()->getObjectAt( model->inverseTranslate( e->pos() ), pIndex ), model->inverseTranslate( e->pos() ) );
            model->setCopyedRotation( 0 );
            if( !model->getCopyedObject().isNull() )
                model->getWindow()->setMode( PIPETTE_SET );
        }
    }
    ArbeitsController::mouseReleaseEvent( e );
}

/*
 * Controller für den PIPETTE_SET Modus
 */

PasteController::PasteController( ArbeitsModel *m )
    : ArbeitsController( m )
{}

void PasteController::mouseMoveEvent( QMouseEvent *e )
{ // Setzen der Position und der Rotierung des kopierten Objektes
    if( model->isMouseButtonPressed( Qt::RightButton ) )
    {
        if( !model->getCopyedObject().isNull() )
            model->setCopyedRotation( model->getCopyedRotation() + e->pos().x() - model->getMousePoint().x() );
    }
    ArbeitsController::mouseMoveEvent( e );
}

void PasteController::mouseReleaseEvent( QMouseEvent *e )
{ // Einfpgen des Objektes oder der Objekt-ID
    if( model->isMouseButtonPressed( Qt::LeftButton ) && e )
    {
        if( model->getFrame() )
        {
            model->getFrame()->setObjectAt( model->inverseTranslate( e->pos() ), model->getCopyedObject(), model->getCopyedCenter(), model->getCopyedRotation() );
            model->getWindow()->setupFrameTree();
            int pIndex;
            model->setCopyedObject( model->getFrame()->getObjectAt( model->inverseTranslate( e->pos() ), pIndex ), model->inverseTranslate( e->pos() ) );
            model->setCopyedRotation( 0 );
        }
    }
    ArbeitsController::mouseReleaseEvent( e );
}

/*
 * Controller für den NEW Modus
 */

NewController::NewController( ArbeitsModel *m )
    : ArbeitsController( m )
{}

void NewController::mouseReleaseEvent( QMouseEvent *e )
{
    if( model->isMouseButtonPressed( Qt::LeftButton ) && e )
    { // Hinzufügen von Punkten zum neuen Polygon
        if( !model->getNewPolygon() )
            model->setNewPolygon( new QPolygon() );
        QPoint pos = model->inverseTranslate( e->pos() );
        if( model->getNewPolygon()->size() > 3 &&
            pos.x() > model->getNewPolygon()->begin()->x() - 10 && pos.x() < model->getNewPolygon()->begin()->x() + 10 &&
            pos.y() > model->getNewPolygon()->begin()->y() - 10 && pos.y() < model->getNewPolygon()->begin()->y() + 10 )
        { // Polygon ist fertig und wird zur Sequenz hinzugefügt
            QList<QPolygon> polygons;
            polygons.append( *model->getNewPolygon() );
            if( model->getFrame() )
                model->getFrame()->addObject( "-1", 0, polygons );
            model->setNewPolygon( 0 );
            model->getWindow()->setupFrameTree();
        }
        else
            model->getNewPolygon()->append( pos );
    }
    if( model->isMouseButtonPressed( Qt::RightButton ) && e )
    { // Letzten Punkt vom Polygon entfernen
        if( model->getNewPolygon() )
        {
            if( model->getNewPolygon()->size() == 1 )
                model->setNewPolygon( 0 );
            else
                model->getNewPolygon()->pop_back();
        }
    }
    ArbeitsController::mouseReleaseEvent( e );
}

void NewController::restartController()
{ // Neues Polygon beenden und zur Sequenz hinzufügen
    if( model->getNewPolygon() && model->getNewPolygon()->size() >= 3 )
    {
        QList<QPolygon> polygons;
        polygons.append( *model->getNewPolygon() );
        model->getFrame()->addObject( "-1", 0, polygons );
        model->setNewPolygon( 0 );
        model->getWindow()->setupFrameTree();
        ArbeitsController::restartController();
    }
}

/*
 * Controller für den DELETE Modus
 */

DeleteController::DeleteController( ArbeitsModel *m )
    : ArbeitsController( m )
{}

void DeleteController::mousePressEvent( QMouseEvent *e )
{ // Setzt den Beginn des auswahl feldes
    if( e && e->button() == Qt::LeftButton )
    {
        if (!model->getDeleteField() )
            model->setDeleteField( new QRect(e->pos(), QSize()) );
    }
    ArbeitsController::mousePressEvent( e );
}

void DeleteController::mouseMoveEvent( QMouseEvent *e )
{ // Setze das Ende des Auswahl Feldes
    if( e && model->isMouseButtonPressed( Qt::LeftButton ) && model->getDeleteField() )
    {
        *model->getDeleteField() = QRect( model->getMousePressPoint(), e->pos() ).normalized();
    }
    ArbeitsController::mouseMoveEvent( e );
}

void DeleteController::mouseReleaseEvent( QMouseEvent *e )
{ // Lösche alle Eckpunkte im ausgewählten Feld
    if( model->isMouseButtonPressed( Qt::LeftButton ) && e && model->getDeleteField() && model->getFrame() )
    {
        if( model->getDeleteField()->size() == QSize( 0, 0 ) )
            model->getFrame()->selectObjectAt( model->inverseTranslate( e->pos() ) );
        else
        {
            QRect field = *model->getDeleteField();
            model->getFrame()->removeSelectedVertices(QRect( model->inverseTranslate( field.topLeft() ), model->inverseTranslate( field.bottomRight() ) ));
            delete model->getDeleteField();
            model->setDeleteField( 0 );
            model->getWindow()->setupFrameTree();
        }
    }
    ArbeitsController::mouseReleaseEvent( e );
}

/*
 * Controller für den ZOOM_IN Modus
 */

ZoomInController::ZoomInController( ArbeitsModel *m )
    : ArbeitsController( m )
{}

void ZoomInController::mousePressEvent( QMouseEvent *e )
{ // Setzte den Startpunkt des sichtbaren Gebiets
    if( e->button() == Qt::LeftButton )
    {
        if (!model->getDeleteField() )
            model->setDeleteField( new QRect(e->pos(), QSize()) );
    }
    ArbeitsController::mousePressEvent( e );
}

void ZoomInController::mouseMoveEvent( QMouseEvent *e )
{ // Setzte den Endpunkt des sichtbaren Gebiets
    if( model->isMouseButtonPressed( Qt::LeftButton ) && model->getDeleteField() && e )
    {
        *model->getDeleteField() = QRect( model->getMousePressPoint(), e->pos() ).normalized();
    }
    ArbeitsController::mouseMoveEvent( e );
}

void ZoomInController::mouseReleaseEvent( QMouseEvent *e )
{ // Aktualisiere die Ansicht auf das ausgewählte Gebiet
    if( model->isMouseButtonPressed( Qt::LeftButton ) && e && model->getFrame() && model->getDeleteField() )
    {
        if( model->getDeleteField()->size() == QSize( 0, 0 ) )
            model->getFrame()->selectObjectAt( model->inverseTranslate( e->pos() ) );
        else
        {
            QRect field = *model->getDeleteField();
            QPoint leftTop = model->inverseTranslate( field.topLeft() );
            int xOffset = leftTop.x(), yOffset = leftTop.y();
            if( xOffset < 0 )
                xOffset = 0;
            if( yOffset < 0 )
                yOffset = 0;
            QPoint bottomRight = model->inverseTranslate( field.bottomRight() );
            if( bottomRight.x() >= model->getImage().width() )
                bottomRight.setX( model->getImage().width() );
            if( bottomRight.y() >= model->getImage().height() )
                bottomRight.setY( model->getImage().height() );
            if( bottomRight.x() > xOffset && bottomRight.y() > yOffset )
            {
                QSize s = model->getViewSize();
                QRect imgRect( QPoint( 0, 0 ), s );
                QSize imgS = model->getImage().size();
                if( imgS.width() > s.width() )
                {
                    imgS.scale( s, Qt::KeepAspectRatio );
                    imgRect.setSize( imgS );
                }
                model->setScaleFactor( imgRect.width() / (float)(bottomRight.x() - xOffset), imgRect.height() / (float)(bottomRight.y() - yOffset));
                model->setOffset( xOffset, yOffset );
            }
            delete model->getDeleteField();
            model->setDeleteField( 0 );
            model->getWindow()->setupFrameTree();
        }
    }
    ArbeitsController::mouseReleaseEvent( e );
}