/*
    QAD - Qt ADvanced

    Ivan Pelipenko peri4ko@yandex.ru, Andrey Bychkov work.a.b@yandex.ru

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 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 Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef BLOCKVIEW_H
#define BLOCKVIEW_H

#include "blockbusitem.h"
#include "blockviewwavetrace.h"
#include "qad_blockview_export.h"

#include <QDebug>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QMouseEvent>
#include <QPainter>
#include <QPixmap>
#include <QPropertyAnimation>
#include <QTime>


Q_DECLARE_METATYPE(BlockItem *)
Q_DECLARE_METATYPE(BlockItemPin *)
Q_DECLARE_METATYPE(BlockBusItem *)


class QAD_BLOCKVIEW_EXPORT BlockView: public QGraphicsView {
	Q_OBJECT
	Q_ENUMS(SelectionMode)

	Q_PROPERTY(bool gridVisible READ isGridVisible WRITE setGridVisible)
	Q_PROPERTY(bool snapToGrid READ isSnapToGrid WRITE setSnapToGrid)
	Q_PROPERTY(QPen gridPen READ gridPen WRITE setGridPen)
	Q_PROPERTY(double gridStep READ gridStep WRITE setGridStep)
	Q_PROPERTY(double gridPointsWidth READ gridPointsWidth WRITE setGridPointsWidth)
	Q_PROPERTY(SelectionMode selectionMode READ selectionMode WRITE setSelectionMode)
	Q_PROPERTY(bool postMoveConnect READ isPostMoveConnectEnabled WRITE setPostMoveConnectEnabled)
	Q_PROPERTY(bool connectByMouse READ isConnectByMouseEnabled WRITE setConnectByMouseEnabled)
	Q_PROPERTY(bool navigationEnabled READ isNavigationEnabled WRITE setNavigationEnabled)
	Q_PROPERTY(bool navigateAnimationEnabled READ isNavigateAnimationEnabled WRITE setNavigateAnimationEnabled)
	Q_PROPERTY(bool blockAnimationEnabled READ isBlockAnimationEnabled WRITE setBlockAnimationEnabled)
	Q_PROPERTY(bool traceConsiderBuses READ isTraceConsiderBuses WRITE setTraceConsiderBuses)
	Q_PROPERTY(bool pinMulticonnect READ isPinMulticonnectEnabled WRITE setPinMulticonnectEnabled)
	Q_PROPERTY(bool miniMap READ isMiniMapEnabled WRITE setMiniMapEnabled)
	Q_PROPERTY(bool zoomWheelOnly READ isZoomWheelOnly WRITE setZoomWheelOnly)
	Q_PROPERTY(bool busSquareNodes READ isBusSquareNodes WRITE setBusSquareNodes)

	Q_PROPERTY(double _thumb READ _thumb WRITE _setThumb DESIGNABLE false SCRIPTABLE false)
	Q_PROPERTY(QRectF _nav READ _nav WRITE _setNav DESIGNABLE false SCRIPTABLE false)

	friend class BlockBusItem;
	friend class BlockItemPin;

public:
	BlockView(QWidget * parent = 0);
	BlockView(QGraphicsScene * scene, QWidget * parent = 0);
	~BlockView();

	enum SelectionMode {
		NoSelection,
		SingleSelection,
		MultiSelection
	};

	QPen gridPen() const { return grid_pen; }
	bool isGridVisible() const { return grid_visible; }
	bool isSnapToGrid() const { return grid_snap; }
	bool isPostMoveConnectEnabled() const { return pm_connect; }
	bool isNavigationEnabled() const { return navigation; }
	bool isNavigateAnimationEnabled() const { return is_nav_anim; }
	bool isBlockAnimationEnabled() const { return is_block_anim; }
	bool isConnectByMouseEnabled() const { return m_connect; }
	bool isTraceConsiderBuses() const { return m_trace_with_buses; }
	bool isPinMulticonnectEnabled() const { return m_pin_mc; }
	bool isMiniMapEnabled() const { return minimap; }
	bool isZoomWheelOnly() const { return wheel_zoom; }
	bool isBusSquareNodes() const { return square_node; }
	double gridStep() const { return grid_step; }
	double gridPointsWidth() const { return grid_points; }
	SelectionMode selectionMode() const { return smode; }
	void setSelectionMode(SelectionMode mode) { smode = mode; }

	void addItems(QList<QGraphicsItem *> items) {
		foreach(QGraphicsItem * i, items)
			addItem(i);
	}
	QList<BlockBusItem *> buses() const;
	QList<BlockBusItem *> wrongConnectedBuses() const;
	QList<BlockItem *> blocks() const;
	QList<QGraphicsItem *> decors() const;
	BlockBusItem * connectionBus(BlockItem * b0, BlockItem * b1) const;
	QList<BlockBusItem *> connectionBuses(BlockItem * b0, BlockItem * b1) const;
	bool connectPins(BlockItemPin * p0, BlockItemPin * p1);
	QList<BlockItem *> selectedBlocks() const;
	QList<QGraphicsItem *> selectedDecors() const;

	void setTransform(const QTransform & matrix, bool combine = false);
	void centerOn(const QPointF & pos);
	void centerOn(qreal x, qreal y);
	void centerOn(const QGraphicsItem * item);

	void fitInView(const QRectF & rect, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio);
	void fitInView(qreal x, qreal y, qreal w, qreal h, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio);
	void fitInView(const QGraphicsItem * item, Qt::AspectRatioMode aspectRatioMode = Qt::IgnoreAspectRatio);
	void fitInView();

	QRectF itemsBoundingRect() const;

protected:
	void _init();
	void _updateBack();
	bool event(QEvent * e);
	bool eventFilter(QObject * o, QEvent * e);
	void stopTimer(int & tid);
	void restartTimer(int & tid, int msecs);
	void timerEvent(QTimerEvent * e);
	void wheelEvent(QWheelEvent * e);
	void mousePressEvent(QMouseEvent * event);
	void mouseReleaseEvent(QMouseEvent * event);
	void mouseMoveEvent(QMouseEvent * event);
	void mouseDoubleClickEvent(QMouseEvent * event);
	void keyPressEvent(QKeyEvent * event);
	void keyReleaseEvent(QKeyEvent * e);
	void resizeEvent(QResizeEvent * event);
	void scrollContentsBy(int dx, int dy);
	void drawBackground(QPainter * painter, const QRectF & rect);
	void drawThumb();
	void drawSceneThumb();
	void thumbHide();
	void thumbShow();
	void restoreSelState();
	void saveSelState();
	void saveBusesState();
	void restoreBusesState();
	void applySelRect(QGraphicsSceneMouseEvent * me);
	void applyGridStep();
	void trace(QPointF scene_pos_from, QPointF scene_pos_to, BlockBusItem * bus, bool primary = true);
	void clearBusStates();
	void matchBus();
	bool connectTmpToBus(BlockBusItem * bus);
	void markPins(int bus_type);
	void unmarkPins(bool to_normal = false);
	void hoverAcceptedPin(BlockItemPin * pin, bool hover);
	void unhoverPins(BlockItemPin * excl_pin = 0);
	void simplifyBuses();
	bool moveBuses(const QList<QGraphicsItem *> & items, QPointF dp);
	QList<BlockBusItem *> internalBuses(const QList<BlockItem *> & items);
	QList<BlockItemPin *> nearPins(BlockItemPin * pin, Qt::KeyboardModifiers km);
	BlockItemPin * getPin(const QList<QGraphicsItem *> & list) const;
	void highlightNearPins(BlockItemPin * pin, Qt::KeyboardModifiers km);
	void hideTmpBuses(bool clear = true);
	double _thumb() const { return _talpha; }
	QRectF _nav() const;
	void animateNav(QRectF d, double scl = 0.);
	void scrollFromThumb();
	void deleteCopyTemp();
	void emitActionEvent(BlockItemBase::Action action, QList<QGraphicsItem *> items);
	void setGhost(BlockItem * item);
	void clearGhost();
	void startNewBus(int bus_type);
	void maybeEndNewBus();
	BlockItem * ghost() const { return ghost_; }

	virtual void loadBus(BlockBusItem * bus) {}
	virtual void copyBlocks(QList<BlockItem *> items, QPointF offset) {}
	virtual void copyBuses(QList<BlockBusItem *> items, QPointF offset) {}
	virtual void newBusStarted(int bus_type) {}
	virtual void newBusCreated(BlockBusItem * bus) {}
	virtual void newBusFinished() {}

	QGraphicsScene * scene_;
	QGraphicsRectItem sel_rect;
	QGraphicsItem * mm_ci;
	QList<QGraphicsItem *> sel_items;
	QList<BlockItem *> copy_items;
	QList<BlockItemPin *> last_multiconnect_pl;
	QList<BlockBusItem *> copy_buses, tmp_buses;
	BlockBusItem tmp_bus, *match_bus, *bus_from, *cur_bus;
	BlockItemPin * hpin;
	BlockItem * ghost_;
	BlockViewWavetrace wavetrace;
	QPoint press_point, screen_point, thumb_press;
	QPointF scene_point, trace_from, last_trace_from, trace_to, copy_dp;
	QRect thumb_sr, thumb_vr;
	QRectF nav_target, nav_rect;
	QSizeF thumb_size;
	QPen grid_pen;
	SelectionMode smode;
	QImage im_scene;
	QWidget widget_thumb;
	Qt::KeyboardModifiers mm_mods;
	QPropertyAnimation thumb_anim, nav_anim;
	int timer_thumb, thumb_hide_delay, thick;
	bool mm_drag, new_bus, new_branch, moved, mm_cancel, iconnect, mm_copy, mm_thumb, ae_enabled, is_nav_anim, is_block_anim,
		move_bus_point;
	bool grid_visible, grid_snap, pm_connect, navigation, m_connect, m_trace_with_buses, m_pin_mc, minimap, prev_tcb, wheel_zoom;
	bool nav_prev_aa, nav_prev_imaa, nav_prev_grid, square_node, block_emit_selection, new_bus_started;
	double grid_step, grid_points, cur_scl, _talpha, thumb_scl;
	double prev_app_scale;

protected slots:
	void getPinMC(bool * v);
	void adjustThumb();
	void newBranch(BlockBusItem * item);
	void newBranchTrace(BlockBusItem * item, QPointF to);
	void newBranchAccept(BlockBusItem * item);
	void newBranchCancel();
	void removedBus(QObject * o);
	void removedBlock(QObject * o);
	void removeJunk();
	void sceneSelectionChanged();
	void updateNavRect();
	void scrolled();
	void _setThumb(double v);
	void _setNav(QRectF v);
	void _navFinished();
	void startBusPointMove(int bus_type);
	void pinHoverInOut(BlockItemPin * pin);
	void checkPaste(bool queued);
	void checkPaste() { checkPaste(false); }

public slots:
	void setGridPen(const QPen & pen) {
		grid_pen = pen;
		_updateBack();
	}
	void setGridVisible(bool yes) {
		grid_visible = yes;
		_updateBack();
	}
	void setSnapToGrid(bool yes) { grid_snap = yes; }
	void setGridStep(double step) {
		grid_step = step;
		applyGridStep();
		_updateBack();
	}
	void setGridPointsWidth(double width_) {
		grid_points = width_;
		_updateBack();
	}
	void setPostMoveConnectEnabled(bool on) { pm_connect = on; }
	void setNavigationEnabled(bool on) { navigation = on; }
	void setNavigateAnimationEnabled(bool on) { is_nav_anim = on; }
	void setBlockAnimationEnabled(bool on) { is_block_anim = on; }
	void setConnectByMouseEnabled(bool on) { m_connect = on; }
	void setTraceConsiderBuses(bool on) { m_trace_with_buses = on; }
	void setPinMulticonnectEnabled(bool on) { m_pin_mc = on; }
	void setMiniMapEnabled(bool on) { minimap = on; }
	void setZoomWheelOnly(bool on) { wheel_zoom = on; }
	void setBusSquareNodes(bool yes);

	void zoom(double factor);
	void zoomIn() { zoom(1.2); }
	void zoomOut() { zoom(1. / 1.2); }
	void zoomReset();

	void copyToClipboard();
	void pasteFromClipboard();

	void reconnectAll();
	void selectNone();
	void selectAll();
	void removeSelected();
	void removeAll();
	void clearSelection();
	void addItem(QGraphicsItem * item, bool emit_action = true);

signals:
	void blockDoubleClicked(BlockItem *);
	void blockHoverEnter(BlockItem *);
	void blockHoverLeave(BlockItem *);
	void busDoubleClicked(BlockBusItem *);
	void schemeAction(BlockItemBase::Action action, QList<QGraphicsItem *> items);
	void blockRemoved(BlockItem * item);
	void connectionsChanged();
	void copyEnabledChanged(bool);
	void pasteEnabledChanged(bool);
	void selectionChanged();
};

#endif // BLOCKVIEW_H
