// Copyright (c) 2012-2017 VideoStitch SAS
// Copyright (c) 2018 stitchEm

#include "boundedValue.hpp"

#include <Eigen/Dense>
#include <iostream>

#include <limits>

namespace VideoStitch {
namespace Calibration {

BoundedValue::BoundedValue() : modifier(new std::array<double, 4>) {
  /*By default, bound is [-1; 1]*/
  shift = 0;
  scale = 1;
  Eigen::Map<Eigen::Matrix<double, 2, 2, Eigen::RowMajor> > R(modifier->data());
  R.setIdentity();
}

BoundedValue::~BoundedValue() {}

bool BoundedValue::isConstant() const { return (scale <= std::numeric_limits<double>::epsilon()); }

void BoundedValue::setBounds(double min, double max) {
  shift = 0.5 * (min + max);
  scale = 0.5 * (max - min);
  Eigen::Map<Eigen::Matrix<double, 2, 2, Eigen::RowMajor> > R(modifier->data());
  R.setIdentity();
}

double *BoundedValue::getMinimizerPtr() const { return modifier->data(); }

void BoundedValue::setMinimizerValues(const double *ptr) {
  (*modifier)[0] = ptr[0];
  (*modifier)[1] = ptr[1];
  (*modifier)[2] = ptr[2];
  (*modifier)[3] = ptr[3];
}

double BoundedValue::getValue() const {
  Eigen::Map<Eigen::Matrix<double, 2, 2, Eigen::RowMajor> > R(modifier->data());

  Eigen::Vector2d vec;
  vec(0) = 0;
  vec(1) = 1;

  Eigen::Vector2d updated_vec = (Eigen::Vector2d)(R * vec);

  double val = scale * updated_vec(0) + shift;
  return val;
}

void BoundedValue::getJacobian(Eigen::Matrix<double, 1, 4> &J) const {
  Eigen::Map<Eigen::Matrix<double, 2, 2, Eigen::RowMajor> > R(modifier->data());

  Eigen::Vector2d vec;
  vec(0) = 0;
  vec(1) = 1;
  Eigen::Vector2d updated_vec = (Eigen::Vector2d)(R * vec);

  /* scale [1 0; 0 0] * update * R * vec */
  /* scale * R(0, 0) * updated_vec(0) +  scale * R(0, 1) * updated_vec(1) */

  J(0, 0) = scale * updated_vec(0);
  J(0, 1) = 0;
  J(0, 2) = scale * updated_vec(1);
  J(0, 3) = 0;
}

void BoundedValue::setValue(double val) {
  Eigen::Map<Eigen::Matrix<double, 2, 2, Eigen::RowMajor> > R(modifier->data());

  /*Scale == 0 means we have no uncertainty, shift IS the value*/
  if (fabs(scale) < 1e-12) {
    R.setIdentity();
    return;
  }

  double b = (val - shift) / scale;
  double angle = -asin(b);

  assert(std::abs(b) <= 1.);
  R(0, 0) = cos(angle);
  R(0, 1) = -sin(angle);
  R(1, 0) = sin(angle);
  R(1, 1) = cos(angle);
}

void BoundedValue::tieTo(const BoundedValue &other) {
  shift = other.shift;
  scale = other.scale;
  modifier = other.modifier;
}

void BoundedValue::untie() {
  // create a new modifier object, with the content of the old one
  std::shared_ptr<std::array<double, 4> > tmp_copy = modifier;
  modifier.reset(new std::array<double, 4>);
  *modifier = *tmp_copy;
}

}  // namespace Calibration
}  // namespace VideoStitch