/* ----------------------------------------------------------------- */
/*                                                                   */
/*  Copyright (c) 2010-2013  hkrn                                    */
/*                                                                   */
/* All rights reserved.                                              */
/*                                                                   */
/* Redistribution and use in source and binary forms, with or        */
/* without modification, are permitted provided that the following   */
/* conditions are met:                                               */
/*                                                                   */
/* - Redistributions of source code must retain the above copyright  */
/*   notice, this list of conditions and the following disclaimer.   */
/* - Redistributions in binary form must reproduce the above         */
/*   copyright notice, this list of conditions and the following     */
/*   disclaimer in the documentation and/or other materials provided */
/*   with the distribution.                                          */
/* - Neither the name of the MMDAI project team nor the names of     */
/*   its contributors may be used to endorse or promote products     */
/*   derived from this software without specific prior written       */
/*   permission.                                                     */
/*                                                                   */
/* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND            */
/* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,       */
/* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF          */
/* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE          */
/* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS */
/* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,          */
/* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED   */
/* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,     */
/* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON */
/* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,   */
/* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY    */
/* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE           */
/* POSSIBILITY OF SUCH DAMAGE.                                       */
/* ----------------------------------------------------------------- */

#ifndef VPVM_SCENEWIDGET_H
#define VPVM_SCENEWIDGET_H

#include "SceneLoader.h"

#include <vpvl2/Common.h>
#include <vpvl2/IKeyframe.h>
#include <vpvl2/Scene.h>
#include <vpvl2/extensions/icu4c/StringMap.h>

#include <QBasicTimer>
#include <QElapsedTimer>
#include <QUuid>
#include <QGLWidget>

namespace vpvl2 {
namespace qt {
class DebugDrawer;
class RenderContext;
}
class Factory;
class IBone;
class IEncoding;
class IModel;
class IMorph;
class IMotion;
}

class QGestureEvent;
class QPanGesture;
class QPinchGesture;
class QProgressDialog;
class QSettings;
class QSwipeGesture;

namespace vpvm
{

using namespace vpvl2;

class BackgroundImage;
class Grid;
class InfoPanel;
class Handles;

class SceneWidget : public QGLWidget
{
    Q_OBJECT

public:
    enum EditMode {
        kNone,
        kSelect,
        kRotate,
        kMove
    };

    explicit SceneWidget(const QGLFormat format,
                         IEncoding *encoding,
                         Factory *factory,
                         QSettings *settings,
                         QWidget *parent = 0);
    ~SceneWidget();

    SceneLoader *sceneLoaderRef() const;
    void setWorldGravity(const Vector3 &value);
    void setPreferredFPS(const Scalar &value);
    void setHandlesVisible(bool value);
    void setInfoPanelVisible(bool value);
    void setBoneWireFramesVisible(bool value);
    void startAutomaticRendering();
    void stopAutomaticRendering();
    void loadProject(const QString &filename);
    void saveProject(const QString &filename);

    void loadModel(const QString &path, bool skipDialog = false);
    void loadMotionToAllModels(const QString &path, IMotionSharedPtr motionPtr);
    void loadMotionToSelectedModel(const QString &path, IMotionSharedPtr motionPtr);
    void loadMotionToModel(const QString &path, IModelSharedPtr model, IMotionSharedPtr motionPtr);
    void loadAsset(const QString &path);
    void loadAssetFromMetadata(const QString &path);
    PosePtr insertPoseToSelectedModel(const QString &filename, IModelSharedPtr model);
    IMotionSharedPtr setCameraMotion(const QString &path);
    void makeRay(const QPointF &input, Vector3 &rayFrom, Vector3 &rayTo) const;
    Handles *handlesRef() const { return m_handles.data(); }
    EditMode editMode() const { return m_editMode; }
    const QList<IBone *> &selectedBones() const { return m_selectedBoneRefs; }
    const IKeyframe::TimeIndex &currentTimeIndex() const { return m_timeIndex; }
    bool isGesturesEnabled() const { return m_enableGestures; }
    bool isMoveGestureEnabled() const { return m_enableMoveGesture; }
    bool isRotateGestureEnabled() const { return m_enableRotateGesture; }
    bool isScaleGestureEnabled() const { return m_enableScaleGesture; }
    bool isUndoGestureEnabled() const { return m_enableUndoGesture; }
    bool isDebugDrawingEnabled() const { return m_enableDebugDrawing; }
    bool showModelDialog() const { return m_showModelDialog; }

public slots:
    void clear();
    void setEmptyMotion();
    void insertMotionToAllModels();
    void insertMotionToSelectedModel();
    void deleteSelectedModel();
    void loadFile(const QString &file);
    void setEmptyMotion(IModelSharedPtr model, bool skipWarning);
    void saveMetadataFromAsset(IModelSharedPtr asset);
    void rotateScene(const Vector3 &delta);
    void rotateModel(const Quaternion &delta);
    void rotateModel(IModelSharedPtr model, const Quaternion &delta);
    void translateScene(const Vector3 &delta);
    void translateModel(const Vector3 &delta);
    void translateModel(IModelSharedPtr model, const Vector3 &delta);
    void seekMotion(const IKeyframe::TimeIndex &timeIndex, bool forceCameraUpdate, bool forceEvenSame);
    void resetMotion();
    void setCameraPerspective(const QSharedPointer<ICamera> &camera);
    void setModelEdgeOffset(double value);
    void setModelOpacity(const Scalar &value);
    void setModelPositionOffset(const Vector3 &value);
    void setModelRotationOffset(const Vector3 &value);
    void setModelProjectiveShadowEnable(bool value);
    void setModelSelfShadowEnable(bool value);
    void setModelOpenSkinningEnable(bool value);
    void setModelVertexShaderSkinningType1Enable(bool value);
    void selectBones(const QList<IBone *> &bones);
    void selectMorphs(const QList<IMorph *> &morphs);
    void setEditMode(SceneWidget::EditMode value);
    void setSelectedModel(IModelSharedPtr value) { setSelectedModel(value, kNone); }
    void setSelectedModel(IModelSharedPtr value, SceneWidget::EditMode mode);
    void setBackgroundImage(const QString &filename);
    void revertSelectedModel() { setSelectedModel(IModelSharedPtr(), kNone); }

signals:
    void initailizeGLContextDidDone();
    void fileDidLoad(const QString &filename, bool succeeded);
    void newMotionDidSet(IModelSharedPtr model);
    void modelDidMove(const Vector3 &lastPosition);
    void modelDidRotate(const Quaternion &lastRotation);
    void cameraPerspectiveDidSet(const ICamera *camera);
    void fpsDidUpdate(int fps);
    void handleDidGrab();
    void handleDidRelease();
    void handleDidMoveAbsolute(const Vector3 &position, IBone *bone, int mode);
    void handleDidMoveRelative(const Vector3 &position, IBone *bone, int mode);
    void handleDidRotate(const Scalar &angle, IBone *bone, int mode);
    void bonesDidSelect(const QList<IBone *> &bones);
    void morphsDidSelect(const QList<IMorph *> &morphs);
    void motionDidSeek(const IKeyframe::TimeIndex &timeIndex);
    void undoDidRequest();
    void redoDidRequest();
    void fileDidOpenProgress(const QString &title, bool cancellable);
    void fileDidUpdateProgress(int value, int max, const QString &text);

protected slots:
    void setShowModelDialog(bool value) { m_showModelDialog = value; }

protected:
    bool event(QEvent *event);
    void closeEvent(QCloseEvent *event);
    void dragEnterEvent(QDragEnterEvent *event);
    void dragLeaveEvent(QDragLeaveEvent *event);
    void dragMoveEvent(QDragMoveEvent *event);
    void dropEvent(QDropEvent *event);
    void initializeGL();
    void mousePressEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);
    void mouseReleaseEvent(QMouseEvent *event);
    void paintGL();
    void resizeGL(int w, int h);
    void timerEvent(QTimerEvent *event);
    void wheelEvent(QWheelEvent *event);
    bool gestureEvent(QGestureEvent *event);
    void panTriggered(QPanGesture *event);
    void pinchTriggered(QPinchGesture *event);
    void swipeTriggered(QSwipeGesture *event);

    QScopedPointer<SceneLoader> m_loader;
    QScopedPointer<BackgroundImage> m_background;
    QSettings *m_settingsRef;

private slots:
    void addFile();
    void addModel();
    void addAsset();
    void addAssetFromMetadata();
    void insertPoseToSelectedModel();
    void setBackgroundImage();
    void setBackgroundPosition(const QPoint &value);
    void setBackgroundImageUniformEnable(bool value);
    void clearBackgroundImage();
    void setCameraMotion();
    void resetCamera();
    void resetModelPosition();
    void updatePlaneWorld(const ICamera *camera);
    void renderBackgroundObjects();
    void setGesturesEnable(bool value);
    void zoom(bool up, const Qt::KeyboardModifiers &modifiers);
    void openErrorDialogIfLoadingProjectFailed(bool ok);
    void openErrorDialogIfSavingProjectFailed(bool ok);
    void zoomIn() { zoom(true, Qt::NoModifier); }
    void zoomOut() { zoom(false, Qt::NoModifier); }
    void rotateUp() { rotateScene(Vector3(10.0f, 0.0f, 0.0f)); }
    void rotateDown() { rotateScene(Vector3(-10.0f, 0.0f, 0.0f)); }
    void rotateLeft() { rotateScene(Vector3(0.0f, 10.0f, 0.0f)); }
    void rotateRight() { rotateScene(Vector3(0.0f, -10.0f, 0.0f)); }
    void translateUp() { translateScene(Vector3(0.0f, 1.0f, 0.0f)); }
    void translateDown() { translateScene(Vector3(0.0f, -1.0f, 0.0f)); }
    void translateLeft() { translateScene(Vector3(-1.0f, 0.0f, 0.0f)); }
    void translateRight() { translateScene(Vector3(1.0f, 0.0f, 0.0f)); }
    void translateModelUp() { translateModel(Vector3(0.0f, 0.5f, 0.0f)); }
    void translateModelDown() { translateModel(Vector3(0.0f, -0.5f, 0.0f)); }
    void translateModelLeft() { translateModel(Vector3(-0.5f, 0.0f, 0.0f)); }
    void translateModelRight() { translateModel(Vector3(0.5f, 0.0f, 0.0f)); }
    void refreshScene() { seekMotion(m_timeIndex, true, false); }
    void refreshMotions() { seekMotion(m_timeIndex, false, false); }
    void setMoveGestureEnable(bool value) { m_enableMoveGesture = value; }
    void setRotateGestureEnable(bool value) { m_enableRotateGesture = value; }
    void setScaleGestureEnable(bool value) { m_enableScaleGesture = value; }
    void setUndoGestureEnable(bool value) { m_enableUndoGesture = value; }
    void toggleDebugDrawing() { m_enableDebugDrawing = m_enableDebugDrawing ? false : true; }

private:
    class PlaneWorld;
    void clearSelectedBones();
    void clearSelectedMorphs();
    void updateScene();
    bool acceptReadmeInArchive(Archive *archive, const QStringList &allFilesInArchive, const QString &filename);
    bool acceptAddingModel(const IModel *model);
    bool testHitModelHandle(const QPointF &pos);
    void updateFPS();
    void loadModelMotion(IMotionSharedPtr motion, const QString &path, IModelSharedPtr model);
    void grabImageHandle(const Scalar &deltaValue);
    void grabModelHandleByRaycast(const QPointF &pos,
                                  const QPointF &diff,
                                  int flags);
    IBone *findNearestBone(const IModel *model,
                           const Vector3 &znear,
                           const Vector3 &zfar,
                           const Scalar &threshold) const;
    bool intersectsBone(const IBone *bone,
                        const Vector3 &znear,
                        const Vector3 &zfar,
                        const Scalar &threshold) const;

    QScopedPointer<DebugDrawer> m_debugDrawer;
    QScopedPointer<Grid> m_grid;
    QScopedPointer<InfoPanel> m_info;
    QScopedPointer<PlaneWorld> m_plane;
    QScopedPointer<Handles> m_handles;
    QScopedPointer<qt::RenderContext> m_renderContext;
    StringMap m_config;
    IEncoding *m_encodingRef;
    Factory *m_factoryRef;
    IBone *m_currentSelectedBoneRef;
    QList<IBone *> m_selectedBoneRefs;
    QList<IMorph *> m_selectedMorphRefs;
    QBasicTimer m_updateTimer;
    QElapsedTimer m_refreshTimer;
    QPointF m_clickOrigin;
    QPointF m_delta;
    EditMode m_editMode;
    Vector3 m_lastBonePosition;
    Scalar m_totalDelta;
    IKeyframe::TimeIndex m_timeIndex;
    float m_lastDistance;
    float m_prevElapsed;
    int m_frameCount;
    int m_currentFPS;
    int m_handleFlags;
    bool m_enableBoneMove;
    bool m_enableBoneRotate;
    bool m_showModelDialog;
    bool m_lockTouchEvent;
    bool m_enableGestures;
    bool m_enableMoveGesture;
    bool m_enableRotateGesture;
    bool m_enableScaleGesture;
    bool m_enableUndoGesture;
    bool m_enableUpdateGL;
    bool m_enableDebugDrawing;
    bool m_isImageHandleRectIntersect;

    Q_DISABLE_COPY(SceneWidget)
};

} /* namespace vpvm */

#endif // SCENEWIDGET_H
