/* This file is part of the KDE project
 * Copyright (C) 2007 Martin Pfeiffer <hubipete@gmx.net>
 * Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
   Copyright (C) 2008 Thorsten Zachmann <zachmann@kde.org>
 * Copyright (C) 2010 Thomas Zander <zander@kde.org>
 *
 * 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., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "DefaultToolWidget.h"
#include "DefaultTool.h"

#include <KoInteractionTool.h>
#include <KoCanvasBase.h>
#include <KoCanvasResourceManager.h>
#include <KoShapeManager.h>
#include <KoSelection.h>
#include <KoUnit.h>
#include <commands/KoShapeMoveCommand.h>
#include <commands/KoShapeSizeCommand.h>
#include <commands/KoShapeTransformCommand.h>
#include <KoPositionSelector.h>
#include "SelectionDecorator.h"
#include "DefaultToolTransformWidget.h"

#include <QAction>
#include <QSize>
#include <QRadioButton>
#include <QLabel>
#include <QCheckBox>
#include <QDoubleSpinBox>
#include <QList>
#include <QTransform>

DefaultToolWidget::DefaultToolWidget( KoInteractionTool* tool,
                                    QWidget* parent )
    : QWidget( parent ),
    m_tool(tool),
    m_blockSignals(false)
{
    setupUi( this );

    setUnit( m_tool->canvas()->unit() );

    aspectButton->setKeepAspectRatio( false );
    updatePosition();
    updateSize();

    connect( positionSelector, SIGNAL(positionSelected(KoFlake::Position)),
        this, SLOT(positionSelected(KoFlake::Position)) );

    connect( positionXSpinBox, SIGNAL(editingFinished()), this, SLOT(positionHasChanged()) );
    connect( positionYSpinBox, SIGNAL(editingFinished()), this, SLOT(positionHasChanged()) );

    connect( widthSpinBox, SIGNAL(editingFinished()), this, SLOT(sizeHasChanged()) );
    connect( heightSpinBox, SIGNAL(editingFinished()), this, SLOT(sizeHasChanged()) );

    KoSelection * selection = m_tool->canvas()->shapeManager()->selection();
    connect( selection, SIGNAL(selectionChanged()), this, SLOT(updatePosition()) );
    connect( selection, SIGNAL(selectionChanged()), this, SLOT(updateSize()) );
    KoShapeManager * manager = m_tool->canvas()->shapeManager();
    connect( manager, SIGNAL(selectionContentChanged()), this, SLOT(updatePosition()) );
    connect( manager, SIGNAL(selectionContentChanged()), this, SLOT(updateSize()) );

    connect( m_tool->canvas()->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)),
        this, SLOT(resourceChanged(int,QVariant)) );

    connect (aspectButton, SIGNAL(keepAspectRatioChanged(bool)),
        this, SLOT(aspectButtonToggled(bool)));
}

void DefaultToolWidget::positionSelected( KoFlake::Position position )
{
    m_tool->canvas()->resourceManager()->setResource( DefaultTool::HotPosition, QVariant(position) );
    updatePosition();
}

void DefaultToolWidget::updatePosition()
{
    QPointF selPosition( 0, 0 );
    KoFlake::Position position = positionSelector->position();

    KoSelection * selection = m_tool->canvas()->shapeManager()->selection();
    if( selection->count() )
        selPosition = selection->absolutePosition( position );

    positionXSpinBox->setEnabled( selection->count() );
    positionYSpinBox->setEnabled( selection->count() );

    if (m_blockSignals)
        return;
    m_blockSignals = true;
    positionXSpinBox->changeValue( selPosition.x() );
    positionYSpinBox->changeValue( selPosition.y() );

    QList<KoShape*> selectedShapes = selection->selectedShapes( KoFlake::TopLevelSelection );
    bool aspectLocked = false;
    foreach (KoShape* shape, selectedShapes)
        aspectLocked = aspectLocked | shape->keepAspectRatio();
    aspectButton->setKeepAspectRatio(aspectLocked);
    m_blockSignals = false;
}

void DefaultToolWidget::positionHasChanged()
{
    KoSelection * selection = m_tool->canvas()->shapeManager()->selection();
    if( ! selection->count() )
        return;

    KoFlake::Position position = positionSelector->position();
    QPointF newPos( positionXSpinBox->value(), positionYSpinBox->value() );
    QPointF oldPos = selection->absolutePosition( position );
    if( oldPos == newPos )
        return;

    QList<KoShape*> selectedShapes = selection->selectedShapes( KoFlake::TopLevelSelection );
    QPointF moveBy = newPos - oldPos;
    QVector<QPointF> oldPositions;
    QVector<QPointF> newPositions;
    const int selectedShapesCount = selectedShapes.count();
    oldPositions.reserve(selectedShapesCount);
    newPositions.reserve(selectedShapesCount);
    foreach( KoShape* shape, selectedShapes )
    {
        oldPositions.append( shape->position() );
        newPositions.append( shape->position() + moveBy );
    }
    selection->setPosition( selection->position() + moveBy );
    m_tool->canvas()->addCommand( new KoShapeMoveCommand( selectedShapes, oldPositions, newPositions ) );
    updatePosition();
}

void DefaultToolWidget::updateSize()
{
    QSizeF selSize( 0, 0 );
    KoSelection * selection = m_tool->canvas()->shapeManager()->selection();
    uint selectionCount = selection->count();
    if( selectionCount )
        selSize = selection->boundingRect().size();

    widthSpinBox->setEnabled( selectionCount );
    heightSpinBox->setEnabled( selectionCount );

    if (m_blockSignals)
        return;
    m_blockSignals = true;
    widthSpinBox->changeValue( selSize.width() );
    heightSpinBox->changeValue( selSize.height() );
    m_blockSignals = false;
}

void DefaultToolWidget::sizeHasChanged()
{
    if (aspectButton->hasFocus())
        return;
    if (m_blockSignals)
        return;

    QSizeF newSize( widthSpinBox->value(), heightSpinBox->value() );

    KoSelection *selection = m_tool->canvas()->shapeManager()->selection();
    QRectF rect = selection->boundingRect();

    if( aspectButton->keepAspectRatio() )
    {
        qreal aspect = rect.width() / rect.height();
        if( rect.width() != newSize.width() )
            newSize.setHeight( newSize.width() / aspect );
        else if( rect.height() != newSize.height() )
            newSize.setWidth( newSize.height() * aspect );
    }

    if( rect.width() != newSize.width() || rect.height() != newSize.height() )
    {
        // get the scale/resize center from the position selector
        QPointF scaleCenter = selection->absolutePosition( positionSelector->position() );

        QTransform resizeMatrix;
        resizeMatrix.translate( scaleCenter.x(), scaleCenter.y() );
        // make sure not to divide by 0 in case the selection is a line and has no width. In this case just scale by 1.
        resizeMatrix.scale( rect.width() ? newSize.width() / rect.width() : 1, rect.height() ? newSize.height() / rect.height() : 1);
        resizeMatrix.translate( -scaleCenter.x(), -scaleCenter.y() );

        QList<KoShape*> selectedShapes = selection->selectedShapes( KoFlake::StrippedSelection );
        QVector<QSizeF> oldSizes, newSizes;
        QVector<QTransform> oldState;
        QVector<QTransform> newState;
        const int selectedShapesCount = selectedShapes.count();

        oldSizes.reserve(selectedShapesCount);
        newSizes.reserve(selectedShapesCount);
        oldState.reserve(selectedShapesCount);
        newState.reserve(selectedShapesCount);

        foreach( KoShape* shape, selectedShapes )
        {
            shape->update();
            QSizeF oldSize = shape->size();
            oldState << shape->transformation();
            QTransform shapeMatrix = shape->absoluteTransformation(0);

            // calculate the matrix we would apply to the local shape matrix
            // that tells us the effective scale values we have to use for the resizing
            QTransform localMatrix = shapeMatrix * resizeMatrix * shapeMatrix.inverted();
            // save the effective scale values, without any mirroring portion
            const qreal scaleX = qAbs(localMatrix.m11());
            const qreal scaleY = qAbs(localMatrix.m22());

            // calculate the scale matrix which is equivalent to our resizing above
            QTransform scaleMatrix = (QTransform().scale( scaleX, scaleY ));
            scaleMatrix =  shapeMatrix.inverted() * scaleMatrix * shapeMatrix;

            // calculate the new size of the shape, using the effective scale values
            oldSizes << oldSize;
            QSizeF newSize = QSizeF( scaleX * oldSize.width(), scaleY * oldSize.height() );
            newSizes << newSize;
            shape->setSize( newSize );

            // apply the rest of the transformation without the resizing part
            shape->applyAbsoluteTransformation( scaleMatrix.inverted() * resizeMatrix );
            newState << shape->transformation();
        }
        m_tool->repaintDecorations();
        selection->applyAbsoluteTransformation( resizeMatrix );
        KUndo2Command * cmd = new KUndo2Command(kundo2_i18n("Resize"));
        new KoShapeSizeCommand( selectedShapes, oldSizes, newSizes, cmd );
        new KoShapeTransformCommand( selectedShapes, oldState, newState, cmd );
        m_tool->canvas()->addCommand( cmd );
        updateSize();
        updatePosition();
    }
}

void DefaultToolWidget::setUnit( const KoUnit &unit )
{
    m_blockSignals = true;
    positionXSpinBox->setUnit( unit );
    positionYSpinBox->setUnit( unit );
    widthSpinBox->setUnit( unit );
    heightSpinBox->setUnit( unit );
    m_blockSignals = false;

    updatePosition();
    updateSize();
}

void DefaultToolWidget::resourceChanged( int key, const QVariant & res )
{
    if( key == KoCanvasResourceManager::Unit )
        setUnit(res.value<KoUnit>());
    else if( key == DefaultTool::HotPosition )
    {
        if( res.toInt() != positionSelector->position() )
        {
            positionSelector->setPosition( static_cast<KoFlake::Position>( res.toInt() ) );
            updatePosition();
        }
    }
}

void DefaultToolWidget::aspectButtonToggled(bool keepAspect)
{
    if (m_blockSignals)
        return;
    KoSelection * selection = m_tool->canvas()->shapeManager()->selection();
    foreach (KoShape *shape, selection->selectedShapes(KoFlake::TopLevelSelection)) {
        shape->setKeepAspectRatio(keepAspect);
    }
}
