/* ----------------------------------------------------------------- */
/*                                                                   */
/*  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.                                       */
/* ----------------------------------------------------------------- */

#include "SceneLoader.h"
#include "AssetWidget.h"

#include <vpvl2/vpvl2.h>
#include <vpvl2/qt/Util.h>

#include <QtGui>
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
#include <QtWidgets>
#endif

/* lupdate cannot parse tr() syntax correctly */

namespace vpvm
{

using namespace vpvl2;
using namespace vpvl2::qt;

static const Scalar kMaxFar = 10000;
static const Scalar kMaxAngle = 360;

AssetWidget::AssetWidget(SceneLoader *loaderRef, QWidget *parent)
    : QWidget(parent),
      m_sceneLoaderRef(loaderRef),
      m_assetGroup(new QGroupBox()),
      m_assignGroup(new QGroupBox()),
      m_positionGroup(new QGroupBox()),
      m_rotationGroup(new QGroupBox()),
      m_assetComboBox(new QComboBox()),
      m_modelComboBox(new QComboBox()),
      m_modelBonesComboBox(new QComboBox()),
      m_removeButton(new QPushButton()),
      m_px(createSpinBox(1, -kMaxFar, kMaxFar)),
      m_py(createSpinBox(1, -kMaxFar, kMaxFar)),
      m_pz(createSpinBox(1, -kMaxFar, kMaxFar)),
      m_rx(createSpinBox(0.1, -kMaxAngle, kMaxAngle)),
      m_ry(createSpinBox(0.1, -kMaxAngle, kMaxAngle)),
      m_rz(createSpinBox(0.1, -kMaxAngle, kMaxAngle)),
      m_scale(createSpinBox(0.1, 0.01, 1000)),
      m_opacity(createSpinBox(0.01, 0, 1)),
      m_assetCompleterModel(new QStringListModel()),
      m_scaleLabel(new QLabel()),
      m_opacityLabel(new QLabel()),
      m_currentAssetRef(0),
      m_currentModelRef(0)
{
    QScopedPointer<QVBoxLayout> mainLayout(new QVBoxLayout());
    QScopedPointer<QHBoxLayout> subLayout(new QHBoxLayout());
    /* アクセサリ選択 */
    QScopedPointer<QCompleter> completer(new QCompleter());
    QScopedPointer<QLineEdit> lineEdit(new QLineEdit());
    completer->setModel(m_assetCompleterModel.data());
    lineEdit->setCompleter(completer.take());
    m_assetComboBox->setLineEdit(lineEdit.take());
    connect(m_assetComboBox.data(), SIGNAL(currentIndexChanged(int)), this, SLOT(changeCurrentAsset(int)));
    subLayout->addWidget(m_assetComboBox.data(), 1);
    /* アクセサリ削除 */
    connect(m_removeButton.data(), SIGNAL(clicked()), this, SLOT(deleteCurrentAsset()));
    subLayout->addWidget(m_removeButton.data());
    m_assetGroup->setLayout(subLayout.take());
    mainLayout->addWidget(m_assetGroup.data());
    subLayout.reset(new QHBoxLayout());
    /* 所属先のモデル選択 */
    m_modelComboBox->addItem("");
    connect(m_modelComboBox.data(), SIGNAL(currentIndexChanged(int)), this, SLOT(changeCurrentModel(int)));
    subLayout->addWidget(m_modelComboBox.data());
    /* 所属先のモデルのボーン選択 */
    connect(m_modelBonesComboBox.data(), SIGNAL(currentIndexChanged(int)), this, SLOT(changeParentBone(int)));
    subLayout->addWidget(m_modelBonesComboBox.data());
    m_assignGroup->setLayout(subLayout.take());
    mainLayout->addWidget(m_assignGroup.data());
    /* 位置(X,Y,Z) */
    QScopedPointer<QFormLayout> formLayout(new QFormLayout());
    connect(m_px.data(), SIGNAL(valueChanged(double)), this, SLOT(updatePositionX(double)));
    formLayout->addRow("X", m_px.data());
    connect(m_py.data(), SIGNAL(valueChanged(double)), this, SLOT(updatePositionY(double)));
    formLayout->addRow("Y", m_py.data());
    connect(m_pz.data(), SIGNAL(valueChanged(double)), this, SLOT(updatePositionZ(double)));
    formLayout->addRow("Z", m_pz.data());
    m_positionGroup->setLayout(formLayout.take());
    /* 回転(X,Y,Z) */
    formLayout.reset(new QFormLayout());
    connect(m_rx.data(), SIGNAL(valueChanged(double)), this, SLOT(updateRotation()));
    formLayout->addRow("X", m_rx.data());
    connect(m_ry.data(), SIGNAL(valueChanged(double)), this, SLOT(updateRotation()));
    formLayout->addRow("Y", m_ry.data());
    connect(m_rz.data(), SIGNAL(valueChanged(double)), this, SLOT(updateRotation()));
    formLayout->addRow("Z", m_rz.data());
    m_rotationGroup->setLayout(formLayout.take());
    subLayout.reset(new QHBoxLayout());
    subLayout->addWidget(m_positionGroup.data());
    subLayout->addWidget(m_rotationGroup.data());
    mainLayout->addLayout(subLayout.take());
    subLayout.reset(new QHBoxLayout());
    /* 拡大率 */
    subLayout->addWidget(m_scaleLabel.data());
    connect(m_scale.data(), SIGNAL(valueChanged(double)), this, SLOT(updateScaleFactor(double)));
    subLayout->addWidget(m_scale.data());
    /* 不透明度 */
    subLayout->addWidget(m_opacityLabel.data());
    connect(m_opacity.data(), SIGNAL(valueChanged(double)), this, SLOT(updateOpacity(double)));
    subLayout->addWidget(m_opacity.data());
    mainLayout->addLayout(subLayout.take());
    mainLayout->addStretch();
    retranslate();
    setLayout(mainLayout.take());
    setEnable(false);
}

AssetWidget::~AssetWidget()
{
}

void AssetWidget::retranslate()
{
    m_assetGroup->setTitle(vpvm::AssetWidget::tr("Asset"));
    m_assignGroup->setTitle(vpvm::AssetWidget::tr("Assign"));
    m_positionGroup->setTitle(vpvm::AssetWidget::tr("Position"));
    m_rotationGroup->setTitle(vpvm::AssetWidget::tr("Rotation"));
    m_removeButton->setText(vpvm::AssetWidget::tr("Remove"));
    m_scaleLabel->setText(vpvm::AssetWidget::tr("Scale"));
    m_opacityLabel->setText(vpvm::AssetWidget::tr("Opacity"));
    m_modelComboBox->setItemText(0, vpvm::AssetWidget::tr("Ground"));
}

void AssetWidget::addAsset(IModelSharedPtr asset)
{
    /* アセットが追加されたらそのアセットが有効になるようにする。また、追加されたら表示を常に有効にする */
    const QString &name = Util::toQStringFromModel(asset.data());
    qDebug("Added an asset to AssetWidget: %s", qPrintable(name));
    m_assets.append(asset);
    m_assetComboBox->addItem(name);
    m_assetComboBox->setCurrentIndex(m_assetComboBox->count() - 1);
    m_assetCompleterModel->setStringList(m_assetCompleterModel->stringList() << name);
    changeCurrentAsset(asset);
    setEnable(true);
}

void AssetWidget::removeAsset(IModelSharedPtr asset)
{
    int index = m_assets.indexOf(asset);
    if (index >= 0) {
        /* 該当するアセットが見つかったら表示項目から削除する */
        m_assets.removeAt(index);
        m_assetComboBox->removeItem(index);
        QStringList assetNames = m_assetCompleterModel->stringList();
        assetNames.removeAt(index);
        m_assetCompleterModel->setStringList(assetNames);
        if (m_assets.count() == 0)
            setEnable(false);
        qDebug("Removed an asset from AssetWidget: %s", qPrintable(Util::toQStringFromModel(asset.data())));
    }
}

void AssetWidget::addModel(IModelSharedPtr model)
{
    /*
     * アセットがモデルの特定ボーンに対して選択できるようにするための処理
     * モデルは SceneLoader が管理するのでポインタのみ。解放してはいけない
     */
    m_models.append(model);
    m_modelComboBox->addItem(Util::toQStringFromModel(model.data()));
    if (model->type() == IModel::kAssetModel)
        addAsset(model);
    qDebug("Added a model to AssetWidget: %s", qPrintable(Util::toQStringFromModel(model.data())));
}

void AssetWidget::removeModel(IModelSharedPtr model)
{
    int index = modelIndexOf(model);
    if (index >= 0) {
        /* モデルが見つかればモデルとそのボーンリストを表示上から削除する。実際にモデルを削除をしない */
        m_models.removeAt(index - 1);
        m_modelComboBox->removeItem(index);
        m_modelComboBox->setCurrentIndex(0);
        m_modelBonesComboBox->clear();
    }
    if (model->type() == IModel::kAssetModel)
        removeAsset(model);
    qDebug("Removed a model from AssetWidget: %s", qPrintable(Util::toQStringFromModel(model.data())));
}

void AssetWidget::deleteCurrentAsset()
{
    /*
     * シグナルを通じて SceneLoader#deleteModel を呼び出して削除する
     * また、削除過程で m_currentAssetRef が変更されてしまうため削除するモデルのポインタを保存しておく
     * (そのまま m_currentAssetRef を渡してしまうと m_currentAssetRef が別の値に変わり別のアクセサリも削除されてしまう)
     */
    IModelSharedPtr currentAssetRef = m_currentAssetRef;
    removeAsset(currentAssetRef);
    emit assetDidRemove(currentAssetRef);
}

void AssetWidget::changeCurrentAsset(int index)
{
    if (index >= 0)
        changeCurrentAsset(m_assets[index]);
}

void AssetWidget::changeCurrentAsset(IModelSharedPtr asset)
{
    /* 現在のアセットの情報を更新する。回転の値はラジアン値から度数に変換しておく */
    const Vector3 &position = asset ? asset->worldPosition() : kZeroV3;
    /* setAssetProperty からも呼ばれるので、シグナル発行前に選択したアセットと同じでないことを確認する */
    bool isAssetChanged = false;
    if (m_currentAssetRef != asset) {
        m_currentAssetRef = asset;
        int index = m_assets.indexOf(asset);
        if (index >= 0)
            m_assetComboBox->setCurrentIndex(index);
        isAssetChanged = true;
    }
    m_px->setValue(position.x());
    m_py->setValue(position.y());
    m_pz->setValue(position.z());
    const Quaternion &rotation = asset ? asset->worldRotation() : Quaternion::getIdentity();
    m_rx->setValue(degree(rotation.x()));
    m_ry->setValue(degree(rotation.y()));
    m_rz->setValue(degree(rotation.z()));
    m_scale->setValue(asset ? asset->scaleFactor() : 1);
    m_opacity->setValue(asset ? asset->opacity() : 1);
    if (isAssetChanged) {
        /* コンボボックスの更新によるシグナル発行でボーン情報が更新されてしまうため、事前にボーンを保存して再設定する */
        IModelSharedPtr model(asset->parentModelRef(), &Scene::deleteModelUnlessReferred);
        updateModelBoneComboBox(model);
        int index = modelIndexOf(model);
        m_modelComboBox->setCurrentIndex(index >= 0 ? index : 0);
        IBone *bone = asset->parentBoneRef();
        if (bone) {
            const QString &name = Util::toQStringFromBone(bone);
            index = m_modelBonesComboBox->findText(name);
            if (index >= 0)
                m_modelBonesComboBox->setCurrentIndex(index);
        }
        emit assetDidSelect(asset);
    }
}

void AssetWidget::changeCurrentModel(int index)
{
    if (index > 0) {
        /* モデルのボーンリストを一旦空にして対象のモデルのボーンリストに更新しておく */
        IModelSharedPtr model = m_models[index - 1];
        m_currentModelRef = model;
        m_currentAssetRef->setParentModelRef(model.data());
        updateModelBoneComboBox(model);
    }
    else if (m_currentAssetRef) {
        /* 「地面」用。こちらは全くボーンを持たないのでボーンリストを削除した上でアセットの親ボーンを無効にしておく */
        m_modelBonesComboBox->clear();
        m_currentAssetRef->setParentModelRef(0);
        m_currentAssetRef->setParentBoneRef(0);
    }
}

void AssetWidget::changeParentBone(int index)
{
    if (index >= 0 && m_currentModelRef) {
        Array<IBone *> bones;
        m_currentModelRef->getBoneRefs(bones);
        IBone *bone = bones.at(index);
        m_currentAssetRef->setParentBoneRef(bone);
    }
    else {
        m_currentAssetRef->setParentBoneRef(0);
    }
}

void AssetWidget::updatePositionX(double value)
{
    if (m_currentAssetRef) {
        Vector3 position = m_currentAssetRef->worldPosition();
        position.setX(value);
        m_currentAssetRef->setWorldPosition(position);
    }
}

void AssetWidget::updatePositionY(double value)
{
    if (m_currentAssetRef) {
        Vector3 position = m_currentAssetRef->worldPosition();
        position.setY(value);
        m_currentAssetRef->setWorldPosition(position);
    }
}

void AssetWidget::updatePositionZ(double value)
{
    if (m_currentAssetRef) {
        Vector3 position = m_currentAssetRef->worldPosition();
        position.setZ(value);
        m_currentAssetRef->setWorldPosition(position);
    }
}

void AssetWidget::updateRotation()
{
    if (m_currentAssetRef) {
        const Quaternion x(Vector3(1, 0, 0), radian(m_rx->value()));
        const Quaternion y(Vector3(0, 1, 0), radian(m_ry->value()));
        const Quaternion z(Vector3(0, 0, 1), radian(m_rz->value()));
        m_currentAssetRef->setWorldRotation(x * y * z);
    }
}

void AssetWidget::updateScaleFactor(double value)
{
    if (m_currentAssetRef)
        m_currentAssetRef->setScaleFactor(static_cast<float>(value));
}

void AssetWidget::updateOpacity(double value)
{
    if (m_currentAssetRef)
        m_currentAssetRef->setOpacity(static_cast<float>(value));
}

void AssetWidget::setAssetProperties(IModelSharedPtr asset)
{
    if (asset && asset->type() == IModel::kAssetModel) {
        const IModel *assetRef = asset.data();
        asset->setWorldPosition(m_sceneLoaderRef->assetPosition(assetRef));
        asset->setWorldRotation(m_sceneLoaderRef->assetRotation(assetRef));
        asset->setScaleFactor(m_sceneLoaderRef->assetScaleFactor(assetRef));
        asset->setOpacity(m_sceneLoaderRef->assetOpacity(assetRef));
        asset->setParentModelRef(m_sceneLoaderRef->assetParentModel(assetRef).data());
        asset->setParentBoneRef(m_sceneLoaderRef->assetParentBone(assetRef));
        changeCurrentAsset(asset);
    }
}

QDoubleSpinBox *AssetWidget::createSpinBox(double step, double min, double max)
{
    QScopedPointer<QDoubleSpinBox> spinBox(new QDoubleSpinBox());
    spinBox->setAlignment(Qt::AlignRight);
    spinBox->setRange(min, max);
    spinBox->setSingleStep(step);
    return spinBox.take();
}

void AssetWidget::setEnable(bool value)
{
    m_removeButton->setEnabled(value);
    m_modelComboBox->setEnabled(value);
    m_modelBonesComboBox->setEnabled(value);
    m_px->setEnabled(value);
    m_py->setEnabled(value);
    m_pz->setEnabled(value);
    m_rx->setEnabled(value);
    m_ry->setEnabled(value);
    m_rz->setEnabled(value);
    m_scale->setEnabled(value);
    m_opacity->setEnabled(value);
}

void AssetWidget::updateModelBoneComboBox(IModelSharedPtr model)
{
    /* changeParentBone が呼ばれてしまうので一時的に signal を切る */
    disconnect(m_modelBonesComboBox.data(), SIGNAL(currentIndexChanged(int)), this, SLOT(changeParentBone(int)));
    m_modelBonesComboBox->clear();
    if (model) {
        Array<IBone *> bones;
        model->getBoneRefs(bones);
        const int nbones = bones.count();
        for (int i = 0; i < nbones; i++) {
            IBone *bone = bones[i];
            m_modelBonesComboBox->addItem(Util::toQStringFromBone(bone), i);
        }
    }
    connect(m_modelBonesComboBox.data(), SIGNAL(currentIndexChanged(int)), this, SLOT(changeParentBone(int)));
}

int AssetWidget::modelIndexOf(IModelSharedPtr model)
{
    int index = m_models.indexOf(model);
    /* 最初の要素が地面で、特別枠なため削除してはいけない。そのためインデックスをひとつかさ上げする */
    if (index >= 0)
        index += 1;
    return index;
}

} /* namespace vpvm */
