#include <Optimize/QOptimize.h>

// qt
#include <QFileDialog>
#include <QFontMetrics>
#include <QIntValidator>
#include <QLabel>
#include <QMessageBox>
#include <QResizeEvent>
#include <QThread>
#include <QTimer>

// shapeworks
#include <Optimize/OptimizeParameters.h>

// studio
#include <Data/Preferences.h>
#include <Data/Session.h>
#include <Data/ShapeWorksWorker.h>
#include <Interface/Style.h>
#include <Logging.h>
#include <Optimize/OptimizeTool.h>
#include <Shape.h>
#include <ui_OptimizeTool.h>

using namespace shapeworks;

namespace {
// QLabel that elides its text with "..." when it's narrower than the text.
// The full text is kept as a tooltip so the user can see it on hover.
// sizeHint() reports the full-text width so the layout gives the label its natural
// space when available; elision only happens when the layout must shrink it.
class ElidedLabel : public QLabel {
 public:
  explicit ElidedLabel(const QString& text, QWidget* parent = nullptr) : QLabel(parent), full_text_(text) {
    setToolTip(text);
    updateElidedText();
  }

  QSize sizeHint() const override {
    QFontMetrics metrics(font());
    return QSize(metrics.horizontalAdvance(full_text_) + 4, QLabel::sizeHint().height());
  }

  QSize minimumSizeHint() const override {
    // Minimum: enough for ellipsis plus a couple characters so the label can shrink
    // further if the panel is very narrow, but not to zero width.
    QFontMetrics metrics(font());
    return QSize(metrics.horizontalAdvance(QStringLiteral("X...")), QLabel::minimumSizeHint().height());
  }

 protected:
  void resizeEvent(QResizeEvent* event) override {
    QLabel::resizeEvent(event);
    updateElidedText();
  }

 private:
  void updateElidedText() {
    QFontMetrics metrics(font());
    QString elided = metrics.elidedText(full_text_, Qt::ElideRight, width());
    QLabel::setText(elided);
  }

  QString full_text_;
};
}  // namespace

//---------------------------------------------------------------------------
OptimizeTool::OptimizeTool(Preferences& prefs, Telemetry& telemetry) : preferences_(prefs), telemetry_(telemetry) {
  ui_ = new Ui_OptimizeTool;
  ui_->setupUi(this);

  connect(ui_->optimize_open_button, &QPushButton::toggled, ui_->optimize_content, &QWidget::setVisible);
  connect(ui_->optimize_header, &QPushButton::clicked, ui_->optimize_open_button, &QPushButton::toggle);

  connect(ui_->use_normals, &QCheckBox::toggled, this, &OptimizeTool::update_ui_elements);
  connect(ui_->procrustes, &QCheckBox::toggled, this, &OptimizeTool::update_ui_elements);
  connect(ui_->multiscale, &QCheckBox::toggled, this, &OptimizeTool::update_ui_elements);
  connect(ui_->use_geodesics_from_landmarks, &QCheckBox::toggled, this, &OptimizeTool::update_ui_elements);
  connect(ui_->use_geodesic_distance, &QCheckBox::toggled, this, &OptimizeTool::update_ui_elements);
  connect(ui_->sampling_scale, &QCheckBox::toggled, this, &OptimizeTool::update_ui_elements);

  ui_->number_of_particles->setToolTip("Number of correspondence points to generate");
  ui_->initial_relative_weighting->setToolTip("Relative weighting of correspondence term during initialization");
  ui_->relative_weighting->setToolTip("Relative weighting of correspondence term during optimization");
  ui_->starting_regularization->setToolTip("Starting regularization of correspondence covariance matrix");
  ui_->ending_regularization->setToolTip("Ending regularization of correspondence covariance matrix");
  ui_->iterations_per_split->setToolTip("Number of iterations for each particle split");
  ui_->optimization_iterations->setToolTip("Number of optimizations to run");
  ui_->use_geodesic_distance->setToolTip(
      "Use geodesic distances for sampling term: may be more effective for capturing thin features. "
      "Requires ~10x more time, and larger memory footprint. Only supported for mesh inputs");
  ui_->geodesic_remesh_percent->setToolTip("Percent remesh reduction to use for geodesic distance");
  ui_->use_normals->setToolTip("Use surface normals as part of optimization");
  ui_->normals_strength->setToolTip("Strength of surface normals relative to position");
  ui_->procrustes->setToolTip("Use procrustes registration during optimization");
  ui_->procrustes_interval->setToolTip("How often to run procrustes during optimization (0 = disabled)");
  ui_->procrustes_scaling->setToolTip("Use procrustes scaling");
  ui_->procrustes_rotation_translation->setToolTip("Use procrustes rotation and translation");
  ui_->multiscale->setToolTip("Use multiscale optimization mode");
  ui_->multiscale_particles->setToolTip("Start multiscale optimization after this many particles");
  ui_->use_landmarks->setToolTip("Use landmarks as initial particles");
  ui_->narrow_band->setToolTip(
      "Narrow band around distance transforms.  "
      "This value should only be changed if an error occurs "
      "during optimization suggesting that it should be increased.  "
      "It has no effect on the optimization");
  ui_->shared_boundary->setToolTip("Use shared boundary optimization");
  ui_->shared_boundary_weight->setToolTip("Weight of shared boundary optimization");
  ui_->sampling_scale->setToolTip("Enable sampling gradient scaling");
  ui_->sampling_auto_scale->setToolTip("Automatically scale sampling gradient based on surface area");
  ui_->sampling_scale_value->setToolTip("Scale multiplier for sampling gradient (applied on top of auto-scale when enabled)");
  ui_->use_disentangled_ssm->setToolTip("Use disentangled Optimization technique to build spatiotemporal SSM.");

  // hidden for 6.5 release
  ui_->use_disentangled_ssm->hide();

  QIntValidator* above_zero = new QIntValidator(1, std::numeric_limits<int>::max(), this);
  QIntValidator* zero_and_up = new QIntValidator(0, std::numeric_limits<int>::max(), this);

  QDoubleValidator* double_validator = new QDoubleValidator(0, std::numeric_limits<double>::max(), 1000, this);

  ui_->number_of_particles->setValidator(above_zero);
  ui_->initial_relative_weighting->setValidator(double_validator);
  ui_->relative_weighting->setValidator(double_validator);
  ui_->starting_regularization->setValidator(double_validator);
  ui_->ending_regularization->setValidator(double_validator);
  ui_->iterations_per_split->setValidator(above_zero);
  ui_->optimization_iterations->setValidator(zero_and_up);
  ui_->normals_strength->setValidator(double_validator);
  ui_->procrustes_interval->setValidator(zero_and_up);
  ui_->multiscale_particles->setValidator(above_zero);
  ui_->narrow_band->setValidator(double_validator);
  ui_->geodesics_to_landmarks_weight->setValidator(double_validator);
  ui_->shared_boundary_weight->setValidator(double_validator);
  ui_->sampling_scale_value->setValidator(double_validator);

  line_edits_.push_back(ui_->number_of_particles);
  line_edits_.push_back(ui_->initial_relative_weighting);
  line_edits_.push_back(ui_->relative_weighting);
  line_edits_.push_back(ui_->starting_regularization);
  line_edits_.push_back(ui_->ending_regularization);
  line_edits_.push_back(ui_->iterations_per_split);
  line_edits_.push_back(ui_->optimization_iterations);
  line_edits_.push_back(ui_->normals_strength);
  line_edits_.push_back(ui_->procrustes_interval);
  line_edits_.push_back(ui_->multiscale_particles);
  line_edits_.push_back(ui_->geodesics_to_landmarks_weight);
  line_edits_.push_back(ui_->narrow_band);
  line_edits_.push_back(ui_->shared_boundary_weight);
  line_edits_.push_back(ui_->sampling_scale_value);

  for (QLineEdit* line_edit : line_edits_) {
    connect(line_edit, &QLineEdit::textChanged, this, &OptimizeTool::update_run_button);
  }

  auto set_session_modified = [this]() {
    if (!session_) {
      return;
    }
    session_->set_modified(true);
  };

  auto line_edits = findChildren<QLineEdit*>();
  for (auto line_edit : line_edits) {
    connect(line_edit, &QLineEdit::textChanged, this, set_session_modified);
  }
  auto check_boxes = findChildren<QCheckBox*>();
  for (auto check_box : check_boxes) {
    connect(check_box, &QCheckBox::toggled, this, set_session_modified);
  }

  Style::apply_normal_button_style(ui_->restoreDefaults);
}

//---------------------------------------------------------------------------
OptimizeTool::~OptimizeTool() {}

//---------------------------------------------------------------------------
void OptimizeTool::handle_error(QString msg) {
  SW_LOG(msg.toStdString());
  update_run_button();
}

//---------------------------------------------------------------------------
void OptimizeTool::handle_warning(QString msg) { SW_WARN(msg.toStdString()); }

//---------------------------------------------------------------------------
void OptimizeTool::handle_progress(int val, QString progress_message) {
  if (!optimization_is_running_) {
    return;
  }
  auto particles = optimize_->GetParticles();
  session_->update_particles(particles);
}

//---------------------------------------------------------------------------
void OptimizeTool::handle_optimize_complete() {
  optimization_is_running_ = false;

  auto particles = optimize_->GetParticles();

  session_->update_particles(particles);

  auto procrustes_transforms = optimize_->GetProcrustesTransforms();
  session_->update_procrustes_transforms(procrustes_transforms);

  session_->calculate_reconstructed_samples();
  session_->get_project()->update_subjects();
  Q_EMIT progress(100);

  QString duration = QString::number(elapsed_timer_.elapsed() / 1000.0, 'f', 1);
  SW_LOG("Optimize Complete.  Duration: " + duration.toStdString() + " seconds");

  telemetry_.record_event("optimize", {{"duration_seconds", duration},
                                       {"num_particles", QVariant::fromValue(session_->get_num_particles())}});

  session_->trigger_save();

  Q_EMIT optimize_complete();
  update_run_button();
}

//---------------------------------------------------------------------------
void OptimizeTool::handle_optimize_failed() {
  optimization_is_running_ = false;
  Q_EMIT progress(100);

  std::string duration = QString::number(elapsed_timer_.elapsed() / 1000.0, 'f', 1).toStdString();
  SW_LOG("Optimize Failed.  Duration: " + duration + " seconds");
  Q_EMIT optimize_complete();
  update_run_button();
}

//---------------------------------------------------------------------------
void OptimizeTool::on_run_optimize_button_clicked() {
  // ensure someone doesn't accidentally abort right after clicking RUN
  ui_->run_optimize_button->setEnabled(false);

  if (optimization_is_running_) {
    ui_->run_optimize_button->setText("Aborting...");
    optimization_is_running_ = false;
    shutdown_threads();
    SW_LOG("Aborted Optimize");
    return;
  }

  if (session_->get_filename() == "") {
    SW_ERROR("Project must be saved before running Optimize");
    ui_->run_optimize_button->setEnabled(true);
    return;
  } else {
    session_->trigger_save();
  }

  optimization_is_running_ = true;
  Q_EMIT optimize_start();

  store_params();
  SW_LOG("Please wait: running optimize step...");

  elapsed_timer_.start();
  optimize_ = QSharedPointer<QOptimize>::create();

  session_->clear_particles();

  handle_progress(1, "");
  optimize_parameters_ = QSharedPointer<OptimizeParameters>::create(session_->get_project());

  optimize_parameters_->set_load_callback(std::bind(&OptimizeTool::handle_load_progress, this, std::placeholders::_1));
  optimize_->SetFileOutputEnabled(false);

  // log parameters
  SW_LOG_ONLY("Optimization parameters:");
  for (const auto& pair : optimize_parameters_->get_parameters().get_map()) {
    SW_LOG_ONLY(pair.first + ": " + pair.second);
  }

  ShapeworksWorker* worker =
      new ShapeworksWorker(ShapeworksWorker::OptimizeType, NULL, optimize_, optimize_parameters_, session_);
  QThread* thread = new QThread;
  worker->moveToThread(thread);
  connect(thread, SIGNAL(started()), worker, SLOT(process()));
  connect(worker, SIGNAL(result_ready()), this, SLOT(handle_optimize_complete()));
  connect(worker, &ShapeworksWorker::failure, this, &OptimizeTool::handle_optimize_failed);
  connect(optimize_.data(), &QOptimize::progress, this, &OptimizeTool::handle_progress);
  connect(worker, SIGNAL(finished()), worker, SLOT(deleteLater()));
  connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
  connect(worker, SIGNAL(finished()), thread, SLOT(quit()));
  thread->start();

  threads_ << thread;

  // re-enable after 1 second
  QTimer::singleShot(1000, this, [&]() { update_run_button(); });
}

//---------------------------------------------------------------------------
void OptimizeTool::handle_message(QString s) { SW_LOG(s.toStdString()); }

//---------------------------------------------------------------------------
void OptimizeTool::on_restoreDefaults_clicked() {
  session_->get_project()->clear_parameters(Parameters::OPTIMIZE_PARAMS);
  load_params();
}

//---------------------------------------------------------------------------
void OptimizeTool::set_session(QSharedPointer<Session> session) { session_ = session; }

//---------------------------------------------------------------------------
void OptimizeTool::load_params() {
  setup_domain_boxes();
  auto params = OptimizeParameters(session_->get_project());

  auto num_particles = params.get_number_of_particles();
  for (int i = 0; i < particle_boxes_.size(); i++) {
    int particles = 128;
    if (i < num_particles.size()) {
      particles = num_particles[i];
    }
    particle_boxes_[i]->setText(QString::number(particles));
  }

  ui_->initial_relative_weighting->setText(QString::number(params.get_initial_relative_weighting()));
  ui_->relative_weighting->setText(QString::number(params.get_relative_weighting()));
  ui_->starting_regularization->setText(QString::number(params.get_starting_regularization()));
  ui_->ending_regularization->setText(QString::number(params.get_ending_regularization()));
  ui_->iterations_per_split->setText(QString::number(params.get_iterations_per_split()));
  ui_->optimization_iterations->setText(QString::number(params.get_optimization_iterations()));

  ui_->use_geodesic_distance->setChecked(params.get_use_geodesic_distance());
  ui_->geodesic_remesh_percent->setText(QString::number(params.get_geodesic_remesh_percent()));
  ui_->use_normals->setChecked(params.get_use_normals()[0]);
  ui_->normals_strength->setText(QString::number(params.get_normals_strength()));
  ui_->use_geodesics_from_landmarks->setChecked(params.get_use_geodesics_to_landmarks());
  ui_->geodesics_to_landmarks_weight->setText(QString::number(params.get_geodesic_to_landmarks_weight()));
  ui_->use_disentangled_ssm->setChecked(params.get_use_disentangled_ssm());

  ui_->procrustes->setChecked(params.get_use_procrustes());
  ui_->procrustes_scaling->setChecked(params.get_use_procrustes_scaling());
  ui_->procrustes_rotation_translation->setChecked(params.get_use_procrustes_rotation_translation());
  ui_->procrustes_interval->setText(QString::number(params.get_procrustes_interval()));

  ui_->multiscale->setChecked(params.get_use_multiscale());
  ui_->multiscale_particles->setText(QString::number(params.get_multiscale_particles()));
  ui_->use_landmarks->setChecked(params.get_use_landmarks());
  ui_->narrow_band->setText(QString::number(params.get_narrow_band()));
  ui_->shared_boundary->setChecked(params.get_shared_boundary());
  ui_->shared_boundary_weight->setText(QString::number(params.get_shared_boundary_weight()));
  ui_->sampling_scale->setChecked(params.get_sampling_scale());
  ui_->sampling_auto_scale->setChecked(params.get_sampling_auto_scale());
  ui_->sampling_scale_value->setText(QString::number(params.get_sampling_scale_value()));

  update_ui_elements();
}

//---------------------------------------------------------------------------
void OptimizeTool::store_params() {
  auto params = OptimizeParameters(session_->get_project());

  std::vector<int> num_particles;
  for (int i = 0; i < particle_boxes_.size(); i++) {
    num_particles.push_back(particle_boxes_[i]->text().toInt());
  }
  if (num_particles.empty()) {
    num_particles.push_back(ui_->number_of_particles->text().toInt());
  }

  params.set_number_of_particles(num_particles);
  params.set_initial_relative_weighting(ui_->initial_relative_weighting->text().toDouble());
  params.set_relative_weighting(ui_->relative_weighting->text().toDouble());
  params.set_starting_regularization(ui_->starting_regularization->text().toDouble());
  params.set_ending_regularization(ui_->ending_regularization->text().toDouble());
  params.set_iterations_per_split(ui_->iterations_per_split->text().toDouble());
  params.set_optimization_iterations(ui_->optimization_iterations->text().toDouble());

  params.set_use_geodesic_distance(ui_->use_geodesic_distance->isChecked());
  params.set_geodesic_remesh_percent(ui_->geodesic_remesh_percent->text().toDouble());
  params.set_use_normals({ui_->use_normals->isChecked()});
  params.set_normals_strength(ui_->normals_strength->text().toDouble());
  params.set_use_geodesics_to_landmarks(ui_->use_geodesics_from_landmarks->isChecked());
  params.set_geodesic_to_landmarks_weight(ui_->geodesics_to_landmarks_weight->text().toDouble());
  params.set_use_disentangled_ssm(ui_->use_disentangled_ssm->isChecked());

  params.set_use_procrustes(ui_->procrustes->isChecked());
  params.set_use_procrustes_scaling(ui_->procrustes_scaling->isChecked());
  params.set_use_procrustes_rotation_translation(ui_->procrustes_rotation_translation->isChecked());
  params.set_procrustes_interval(ui_->procrustes_interval->text().toDouble());

  params.set_use_multiscale(ui_->multiscale->isChecked());
  params.set_multiscale_particles(ui_->multiscale_particles->text().toDouble());
  params.set_use_landmarks(ui_->use_landmarks->isChecked());

  // always use preference value
  params.set_geodesic_cache_multiplier(preferences_.get_geodesic_cache_multiplier());
  params.set_optimize_output_prefix(preferences_.get_optimize_file_template().toStdString());

  params.set_narrow_band(ui_->narrow_band->text().toDouble());
  params.set_shared_boundary(ui_->shared_boundary->isChecked());
  params.set_shared_boundary_weight(ui_->shared_boundary_weight->text().toDouble());
  params.set_sampling_scale(ui_->sampling_scale->isChecked());
  params.set_sampling_auto_scale(ui_->sampling_auto_scale->isChecked());
  params.set_sampling_scale_value(ui_->sampling_scale_value->text().toDouble());

  params.save_to_project();
}

//---------------------------------------------------------------------------
void OptimizeTool::enable_actions() { update_run_button(); }

//---------------------------------------------------------------------------
void OptimizeTool::disable_actions() {
  // ui_->run_optimize_button->setEnabled(false);
}

//---------------------------------------------------------------------------
void OptimizeTool::shutdown_threads() {
  std::cerr << "Shut Down Optimization Threads\n";
  if (!optimize_) {
    return;
  }
  if (optimize_parameters_) {
    optimize_parameters_->set_abort_load(true);
  }
  optimize_->AbortOptimization();
}

//---------------------------------------------------------------------------
void OptimizeTool::update_ui_elements() {
  ui_->normals_strength->setEnabled(ui_->use_normals->isChecked());
  ui_->procrustes_scaling->setEnabled(ui_->procrustes->isChecked());
  ui_->procrustes_rotation_translation->setEnabled(ui_->procrustes->isChecked());
  ui_->procrustes_interval->setEnabled(ui_->procrustes->isChecked());
  ui_->multiscale_particles->setEnabled(ui_->multiscale->isChecked());
  ui_->geodesics_to_landmarks_weight->setEnabled(ui_->use_geodesics_from_landmarks->isChecked());
  ui_->geodesic_remesh_percent->setEnabled(ui_->use_geodesic_distance->isChecked());

  bool shared_boundary_available = session_->get_project()->get_domain_names().size() > 1;
  ui_->shared_boundary->setVisible(shared_boundary_available);
  ui_->shared_boundary_weight_label->setVisible(shared_boundary_available);
  ui_->shared_boundary_weight->setVisible(shared_boundary_available);

  ui_->sampling_auto_scale->setEnabled(ui_->sampling_scale->isChecked());
  ui_->sampling_scale_value->setEnabled(ui_->sampling_scale->isChecked());
}

//---------------------------------------------------------------------------
void OptimizeTool::activate() { enable_actions(); }

//---------------------------------------------------------------------------
void OptimizeTool::handle_load_progress(int count) {
  if (!optimization_is_running_) {
    return;
  }

  double value = static_cast<double>(count) / session_->get_shapes().size() * 100.0f;
  if (value < 100) {  // cannot Q_EMIT 100 or the main interface will think the job is done
    Q_EMIT progress(static_cast<int>(value));
  }
}

//---------------------------------------------------------------------------
bool OptimizeTool::validate_inputs() {
  bool all_valid = true;

  std::vector<QLineEdit*> combined = line_edits_;

  combined.insert(combined.end(), particle_boxes_.begin(), particle_boxes_.end());

  for (QLineEdit* line_edit : combined) {
    QString text = line_edit->text();
    int pos;
    QString ss;
    if (QValidator::Acceptable != line_edit->validator()->validate(text, pos)) {
      ss = "QLineEdit {background-color: rgb(255,204,203);}";
      all_valid = false;
    }
    line_edit->setStyleSheet(ss);
  }
  return all_valid;
}

//---------------------------------------------------------------------------
void OptimizeTool::update_run_button() {
  bool inputs_valid = validate_inputs();
  ui_->run_optimize_button->setEnabled(inputs_valid && session_->get_groomed_present());

  if (optimization_is_running_) {
    Style::apply_abort_button_style(ui_->run_optimize_button);
    ui_->run_optimize_button->setText("Abort Optimize");
  } else {
    Style::apply_normal_button_style(ui_->run_optimize_button);
    ui_->run_optimize_button->setText("Run Optimize");
  }
}

//---------------------------------------------------------------------------
void OptimizeTool::setup_domain_boxes() {
  // Clean up any previously added domain widgets from the parent grid
  for (auto* w : domain_grid_widgets_) {
    w->setParent(nullptr);
    delete w;
  }
  domain_grid_widgets_.clear();
  particle_boxes_.clear();

  QLineEdit* last_box = ui_->number_of_particles;

  // Hide the stacked widget and add inputs directly to the parent grid
  // so they share the same columns as the parameter rows below
  ui_->particle_stack->setVisible(false);
  QGridLayout* grid = qobject_cast<QGridLayout*>(ui_->group_box->layout());
  QIntValidator* above_zero = new QIntValidator(1, std::numeric_limits<int>::max(), this);

  if (session_->get_project()->get_number_of_domains_per_subject() < 2) {
    QLineEdit* box = new QLineEdit(this);
    last_box = box;
    box->setAlignment(Qt::AlignHCenter);
    box->setMinimumWidth(50);
    box->setMaximumWidth(100);
    box->setValidator(above_zero);
    box->setText(ui_->number_of_particles->text());
    connect(box, &QLineEdit::textChanged, this, &OptimizeTool::update_run_button);
    particle_boxes_.push_back(box);
    grid->addWidget(box, 0, 2);
    domain_grid_widgets_.push_back(box);
  } else {
    auto domain_names = session_->get_project()->get_domain_names();
    for (int i = 0; i < domain_names.size(); i++) {
      auto label = new ElidedLabel(QString::fromStdString(domain_names[i]), this);
      label->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
      grid->addWidget(label, i, 1);
      domain_grid_widgets_.push_back(label);

      QLineEdit* box = new QLineEdit(this);
      last_box = box;
      box->setAlignment(Qt::AlignHCenter);
      box->setValidator(above_zero);
      connect(box, &QLineEdit::textChanged, this, &OptimizeTool::update_run_button);
      particle_boxes_.push_back(box);
      grid->addWidget(box, i, 2);
      domain_grid_widgets_.push_back(box);
    }
  }

  QWidget::setTabOrder(last_box, ui_->initial_relative_weighting);
  QWidget::setTabOrder(ui_->initial_relative_weighting, ui_->relative_weighting);
  QWidget::setTabOrder(ui_->relative_weighting, ui_->starting_regularization);
  QWidget::setTabOrder(ui_->starting_regularization, ui_->ending_regularization);
  QWidget::setTabOrder(ui_->ending_regularization, ui_->iterations_per_split);
  QWidget::setTabOrder(ui_->iterations_per_split, ui_->optimization_iterations);
  QWidget::setTabOrder(ui_->optimization_iterations, ui_->narrow_band);
  QWidget::setTabOrder(ui_->narrow_band, ui_->use_geodesic_distance);
  QWidget::setTabOrder(ui_->use_geodesic_distance, ui_->geodesic_remesh_percent);
  QWidget::setTabOrder(ui_->geodesic_remesh_percent, ui_->use_normals);
  QWidget::setTabOrder(ui_->use_normals, ui_->normals_strength);
  QWidget::setTabOrder(ui_->normals_strength, ui_->use_geodesics_from_landmarks);
  QWidget::setTabOrder(ui_->use_geodesics_from_landmarks, ui_->geodesics_to_landmarks_weight);
  QWidget::setTabOrder(ui_->geodesics_to_landmarks_weight, ui_->multiscale);
  QWidget::setTabOrder(ui_->multiscale, ui_->multiscale_particles);
  QWidget::setTabOrder(ui_->multiscale_particles, ui_->shared_boundary);
  QWidget::setTabOrder(ui_->shared_boundary, ui_->shared_boundary_weight);
  QWidget::setTabOrder(ui_->shared_boundary_weight, ui_->use_landmarks);
  QWidget::setTabOrder(ui_->use_landmarks, ui_->procrustes);
  QWidget::setTabOrder(ui_->procrustes, ui_->procrustes_interval);
  QWidget::setTabOrder(ui_->procrustes_interval, ui_->procrustes_scaling);
  QWidget::setTabOrder(ui_->procrustes_scaling, ui_->procrustes_rotation_translation);
  QWidget::setTabOrder(ui_->procrustes_rotation_translation, ui_->sampling_scale);
  QWidget::setTabOrder(ui_->sampling_scale, ui_->sampling_scale_value);
  QWidget::setTabOrder(ui_->sampling_scale_value, ui_->sampling_auto_scale);
  QWidget::setTabOrder(ui_->shared_boundary_weight, ui_->use_disentangled_ssm);
  QWidget::setTabOrder(ui_->use_disentangled_ssm, ui_->run_optimize_button);
  QWidget::setTabOrder(ui_->run_optimize_button, ui_->restoreDefaults);
}
