/**
 * Benchmark various OpenCV operations
 *
 * Not particularly rigorous but just getting an idea of the cost of the various operations
 * used in this project.
 *
 * Dave Snowdon, 2017
 */

#include <stdlib.h>
#include <cstring>
#include <opencv2/opencv.hpp>
#include <opencv2/objdetect.hpp>
#include <opencv2/tracking.hpp>

#include <dlib/opencv.h>
#include <dlib/dnn.h>
#include <dlib/image_processing/frontal_face_detector.h>

#include "util.h"

// TODO make the number of iterations configurable
int const TEST_ITERATIONS = 10000;
int const TEST_SLOW_ITERATIONS = 1000;

int const TEST_IMAGE_WIDTH = 500;

int const THRESHOLD_MIN = 127;
int const THRESHOLD_MAX = 255;

int const MOTION_BLUR_KERNEL_SIZE = 21;
int const MOTION_DILATE_KERNEL_SIZE = 3;
cv::Mat const MOTION_DILATE_STRUCTURING = cv::getStructuringElement(cv::MORPH_ELLIPSE,
                                                                    cv::Size(MOTION_DILATE_KERNEL_SIZE,
                                                                             MOTION_DILATE_KERNEL_SIZE));
int const MOTION_ERODE_KERNEL_SIZE = 2;
cv::Mat const MOTION_ERODE_STRUCTURING = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(MOTION_ERODE_KERNEL_SIZE,
                                                                                MOTION_ERODE_KERNEL_SIZE));
cv::Point const MOTION_DILATE_ANCHOR = cv::Point(-1, -1);
int const MOTION_DILATE_ITERATIONS = 2;
double const MOTION_ACCUMULATOR_WEIGHT = 0.5;

dlib::frontal_face_detector face_detector = dlib::get_frontal_face_detector();

dlib::shape_predictor landmark_detector;

// From dnn_face_recognition_ex.cpp
// ----------------------------------------------------------------------------------------

// The next bit of code defines a ResNet network.  It's basically copied
// and pasted from the dnn_imagenet_ex.cpp example, except we replaced the loss
// layer with loss_metric and made the network somewhat smaller.  Go read the introductory
// dlib DNN examples to learn what all this stuff means.
//
// Also, the dnn_metric_learning_on_images_ex.cpp example shows how to train this network.
// The dlib_face_recognition_resnet_model_v1 model used by this example was trained using
// essentially the code shown in dnn_metric_learning_on_images_ex.cpp except the
// mini-batches were made larger (35x15 instead of 5x5), the iterations without progress
// was set to 10000, and the training dataset consisted of about 3 million images instead of
// 55.  Also, the input layer was locked to images of size 150.
template<template<int, template<typename> class, int, typename> class block, int N,
        template<typename> class BN, typename SUBNET>
using residual = dlib::add_prev1<block<N, BN, 1, dlib::tag1<SUBNET>>>;

template<template<int, template<typename> class, int, typename> class block, int N,
        template<typename> class BN, typename SUBNET>
using residual_down = dlib::add_prev2<dlib::avg_pool<2, 2, 2, 2, dlib::skip1<dlib::tag2<block<N, BN, 2, dlib::tag1<SUBNET>>>>>>;

template<int N, template<typename> class BN, int stride, typename SUBNET>
using block  = BN<dlib::con<N, 3, 3, 1, 1, dlib::relu<BN<dlib::con<N, 3, 3, stride, stride, SUBNET>>>>>;

template<int N, typename SUBNET> using ares      = dlib::relu<residual<block, N, dlib::affine, SUBNET>>;
template<int N, typename SUBNET> using ares_down = dlib::relu<residual_down<block, N, dlib::affine, SUBNET>>;

template<typename SUBNET> using alevel0 = ares_down<256, SUBNET>;
template<typename SUBNET> using alevel1 = ares<256, ares<256, ares_down<256, SUBNET>>>;
template<typename SUBNET> using alevel2 = ares<128, ares<128, ares_down<128, SUBNET>>>;
template<typename SUBNET> using alevel3 = ares<64, ares<64, ares<64, ares_down<64, SUBNET>>>>;
template<typename SUBNET> using alevel4 = ares<32, ares<32, ares<32, SUBNET>>>;

using anet_type = dlib::loss_metric<dlib::fc_no_bias<128, dlib::avg_pool_everything<
        alevel0<alevel1<alevel2<alevel3<alevel4<dlib::max_pool<3, 3, 2, 2, dlib::relu<dlib::affine<dlib::con<32, 7, 7, 2, 2,
                dlib::input_rgb_image_sized<150>
        >>>>>>>>>>>>;

// DNN used for face recognition
anet_type face_metrics_net;

cv::Mat example_image;

cv::Mat example_small_image;

cv::Mat example_greyscale;

cv::Mat example_small_greyscale;

cv::Mat example_binary;

cv::Mat example_small_binary;

cv::Mat accumulator;

cv::Mat accumulator_small;

dlib::cv_image<dlib::bgr_pixel> example_dlib;

dlib::cv_image<dlib::bgr_pixel> example_small_dlib;

cv::Mat result_image;

dlib::rectangle face_bounds_large;

dlib::rectangle face_bounds_small;

dlib::full_object_detection landmarks_large;

dlib::full_object_detection landmarks_small;

dlib::matrix<float, 0, 1> face_descriptor_result;

dlib::full_object_detection landmarks_result;

dlib::matrix<dlib::rgb_pixel> face_chip_result;

dlib::matrix<dlib::rgb_pixel> face_image;

std::vector<dlib::matrix<dlib::rgb_pixel>> face_images;

dlib::correlation_tracker tracker_large;

dlib::correlation_tracker tracker_small;

double tracker_confidence_result;

cv::CascadeClassifier faceCascade;

std::vector<cv::Rect> faces_result;

cv::Ptr<cv::Tracker> kcf_tracker_large = cv::TrackerKCF::create();

cv::Ptr<cv::Tracker> kcf_tracker_small = cv::TrackerKCF::create();

cv::Ptr<cv::Tracker> medianflow_tracker_large = cv::TrackerMedianFlow::create();

cv::Ptr<cv::Tracker> medianflow_tracker_small = cv::TrackerMedianFlow::create();

cv::Rect2d opencv_tracker_roi_large;

cv::Rect2d opencv_tracker_roi_small;

// check cost of call via function pointer
void no_op() {
}

void resize_image() {
    double ratio = TEST_IMAGE_WIDTH / (double) example_image.cols;
    int height = (int) std::round(example_image.rows * ratio);
    cv::resize(example_image, result_image, cv::Size(TEST_IMAGE_WIDTH, height), 0, 0, cv::INTER_AREA);
}

void resize_then_greyscale() {
    double ratio = TEST_IMAGE_WIDTH / (double) example_image.cols;
    int height = (int) std::round(example_image.rows * ratio);
    cv::resize(example_image, result_image, cv::Size(TEST_IMAGE_WIDTH, height), 0, 0, cv::INTER_AREA);

    cv::cvtColor(result_image, result_image, cv::COLOR_BGR2GRAY);
}

void greyscale_then_resize() {
    cv::cvtColor(example_image, result_image, cv::COLOR_BGR2GRAY);

    double ratio = TEST_IMAGE_WIDTH / (double) example_image.cols;
    int height = (int) std::round(example_image.rows * ratio);
    cv::resize(result_image, result_image, cv::Size(TEST_IMAGE_WIDTH, height), 0, 0, cv::INTER_AREA);
}

void blur_large() {
    cv::GaussianBlur(example_image, result_image, cv::Size(MOTION_BLUR_KERNEL_SIZE, MOTION_BLUR_KERNEL_SIZE), 0);
}

void blur_small() {
    cv::GaussianBlur(example_small_image, result_image, cv::Size(MOTION_BLUR_KERNEL_SIZE, MOTION_BLUR_KERNEL_SIZE), 0);
}

void frame_difference_large() {
    cv::absdiff(example_image, example_image, result_image);
}

void frame_difference_small() {
    cv::absdiff(example_small_image, example_small_image, result_image);
}

void threshold_large() {
    cv::threshold(example_greyscale, result_image, THRESHOLD_MIN, THRESHOLD_MAX, cv::THRESH_BINARY);
}

void threshold_small() {
    cv::threshold(example_small_greyscale, result_image, THRESHOLD_MIN, THRESHOLD_MAX, cv::THRESH_BINARY);
}

void dilate_large() {
    cv::dilate(example_binary, result_image, MOTION_DILATE_STRUCTURING, MOTION_DILATE_ANCHOR, MOTION_DILATE_ITERATIONS);
}

void dilate_small() {
    cv::dilate(example_small_binary, result_image, MOTION_DILATE_STRUCTURING, MOTION_DILATE_ANCHOR,
               MOTION_DILATE_ITERATIONS);
}

void erode_large() {
    erode(example_binary, result_image, MOTION_ERODE_STRUCTURING);
}

void erode_small() {
    erode(example_small_binary, result_image, MOTION_ERODE_STRUCTURING);
}

void find_contours_large() {
    std::vector<std::vector<cv::Point>> contours;
    std::vector<cv::Vec4i> hierarchy;
    cv::findContours(example_binary, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE, cv::Point(0, 0));
}

void find_contours_small() {
    std::vector<std::vector<cv::Point>> contours;
    std::vector<cv::Vec4i> hierarchy;
    cv::findContours(example_small_binary, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE,
                     cv::Point(0, 0));
}

void norm2_large() {
    double mean = cv::norm(example_binary, example_greyscale, cv::NORM_L2);
}

void norm2_small() {
    double mean = cv::norm(example_small_binary, example_small_greyscale, cv::NORM_L2);
}

void convert_to_float_large() {
    example_greyscale.convertTo(result_image, CV_32FC1);
}

void convert_to_float_small() {
    example_small_greyscale.convertTo(result_image, CV_32FC1);
}

void accumulate_weighted_large() {
    cv::accumulateWeighted(example_greyscale, accumulator, MOTION_ACCUMULATOR_WEIGHT);
}

void accumulate_weighted_small() {
    cv::accumulateWeighted(example_small_greyscale, accumulator_small, MOTION_ACCUMULATOR_WEIGHT);
}

void bitwise_and_large() {
    cv::bitwise_and(example_binary, example_binary, result_image);
}

void bitwise_and_small() {
    cv::bitwise_and(example_small_binary, example_small_binary, result_image);
}

void sum_large() {
    cv::Scalar sum = cv::sum(example_binary);
}

void sum_small() {
    cv::Scalar sum = cv::sum(example_small_binary);
}

void convert_dlib_large() {
    dlib::cv_image<dlib::bgr_pixel> converted(example_image);
}

void convert_dlib_small() {
    dlib::cv_image<dlib::bgr_pixel> converted(example_small_image);
}

void detect_faces_large() {
    std::vector<dlib::rectangle> faceRects = face_detector(example_dlib);
}

void detect_faces_small() {
    std::vector<dlib::rectangle> faceRects = face_detector(example_small_dlib);
}

void detect_faces_opencv_large() {
    /*
     * Using a global vector here to try and prevent the compile from optimising away the call.
     * Expectation that cost of std::vector.clear() should be small relative to the cost of face detection.
     */
    faces_result.clear();
    faceCascade.detectMultiScale(example_greyscale, faces_result, 1.2, 2);
}

void detect_faces_opencv_small() {
    /*
     * Using a global vector here to try and prevent the compile from optimising away the call.
     * Expectation that cost of std::vector.clear() should be small relative to the cost of face detection.
     */
    faces_result.clear();
    faceCascade.detectMultiScale(example_small_greyscale, faces_result, 1.2, 2);
}

void face_landmarks_large() {
    landmarks_result = landmark_detector(example_dlib, face_bounds_large);
}

void face_landmarks_small() {
    landmarks_result = landmark_detector(example_small_dlib, face_bounds_small);
}

void extract_face_chip_large() {
    dlib::extract_image_chip(example_dlib, dlib::get_face_chip_details(landmarks_large, 150, 0.25), face_chip_result);
}

void extract_face_chip_small() {
    dlib::extract_image_chip(example_small_dlib, dlib::get_face_chip_details(landmarks_small, 150, 0.25),
                             face_chip_result);
}

// face descriptors are always computed on the same size image
void compute_face_descriptor() {
    auto descriptors = face_metrics_net(face_images);
    face_descriptor_result = descriptors[0];
}

void correlation_tracker_update_large() {
    tracker_confidence_result = tracker_large.update(example_dlib);
}

void correlation_tracker_update_small() {
    tracker_confidence_result = tracker_small.update(example_small_dlib);
}

void opencv_kcf_tracker_update_large() {
    kcf_tracker_large->update(example_image, opencv_tracker_roi_large);
}

void opencv_kcf_tracker_update_small() {
    kcf_tracker_small->update(example_image, opencv_tracker_roi_small);
}

void opencv_medianflow_tracker_update_large() {
    medianflow_tracker_large->update(example_image, opencv_tracker_roi_large);
}

void opencv_medianflow_tracker_update_small() {
    medianflow_tracker_small->update(example_image, opencv_tracker_roi_small);
}

/*
 * Time an operation specified via a function pointer.
 * We assume that that the time taken to call the function whilst non-zero is small enough to
 * not greatly affect the timings.
 */
void timer(const int num_iterations, void(*operation)(), const char *title) {
    std::cout << "Start: " << title << std::endl;
    double start_time = (double) cv::getTickCount();
    for (int i = 0; i < num_iterations; ++i) {
        operation();
    }
    double end_time = (double) cv::getTickCount();
    double operation_time = (end_time - start_time) / (cv::getTickFrequency() * num_iterations);
    std::cout << "End: " << title << " : " << operation_time << " seconds" << std::endl;
}

int main(int argc, char **argv) {
    if (argc < 2) {
        std::cout << "Usage: <filename>" << std::endl;
        return EXIT_FAILURE;
    }

    char *example_frame = argv[1];
    std::cout << "using " << example_frame << " as test image" << std::endl;

    // facial landmark detector
    dlib::deserialize("models/shape_predictor_5_face_landmarks.dat") >> landmark_detector;

    // DNN used for face recognition
    dlib::deserialize("models/dlib_face_recognition_resnet_model_v1.dat") >> face_metrics_net;

    if (!faceCascade.load("/usr/local/share/OpenCV/haarcascades/haarcascade_frontalface_default.xml")) {
        std::cerr << "Error loading face cascade" << std::endl;
        return 1;
    }

    /*
     * Setup test data
     */
    example_image = cv::imread(example_frame);

    double ratio = TEST_IMAGE_WIDTH / (double) example_image.cols;
    int height = (int) std::round(example_image.rows * ratio);
    cv::resize(example_image, example_small_image, cv::Size(TEST_IMAGE_WIDTH, height), 0, 0, cv::INTER_AREA);

    cv::cvtColor(example_image, example_greyscale, cv::COLOR_BGR2GRAY);
    cv::cvtColor(example_small_image, example_small_greyscale, cv::COLOR_BGR2GRAY);

    cv::threshold(example_greyscale, example_binary, 127, 255, cv::THRESH_BINARY);
    cv::threshold(example_small_greyscale, example_small_binary, 127, 255, cv::THRESH_BINARY);

    example_greyscale.convertTo(accumulator, CV_32FC1);
    example_small_greyscale.convertTo(accumulator_small, CV_32FC1);

    dlib::cv_image<dlib::bgr_pixel> tmp_dlib(example_image);
    example_dlib = tmp_dlib;
    dlib::cv_image<dlib::bgr_pixel> tmp_small_dlib(example_small_image);
    example_small_dlib = tmp_small_dlib;

    std::vector<dlib::rectangle> faceRectsLarge = face_detector(example_dlib);
    if (0 == faceRectsLarge.size()) {
        std::cerr << "Example image must contain at least one face" << std::endl;
        return 1;
    }
    face_bounds_large = faceRectsLarge[0];

    /*
     * dlib needs faces to be about 80x80 pixels in order to detect them
     */
    bool do_small_face_tests = true;
    std::vector<dlib::rectangle> faceRectsSmall = face_detector(example_small_dlib);
    if (0 == faceRectsSmall.size()) {
        std::cerr << "Can't find face in small images, skipping tests" << std::endl;
        do_small_face_tests = false;
    } else {
        face_bounds_small = faceRectsSmall[0];
    }

    landmarks_large = landmark_detector(example_dlib, face_bounds_large);

    dlib::extract_image_chip(example_dlib, dlib::get_face_chip_details(landmarks_large, 150, 0.25), face_image);
    face_images.push_back(face_image);

    // dlib tracker setup
    dlib::rectangle padded_rectangle_large(face_bounds_large.left() - 10,
                                           face_bounds_large.top() - 20,
                                           face_bounds_large.right() + 10,
                                           face_bounds_large.bottom() + 20);
    tracker_large.start_track(example_dlib, padded_rectangle_large);

    // OpenCV tracker setup
    opencv_tracker_roi_large = dlibRectangleToOpenCV(padded_rectangle_large);
    kcf_tracker_large->init(example_image, opencv_tracker_roi_large);
    medianflow_tracker_large->init(example_image, opencv_tracker_roi_large);

    // small image test setup if applicable
    if (do_small_face_tests) {
        landmarks_small = landmark_detector(example_small_dlib, face_bounds_small);

        dlib::rectangle padded_rectangle_small(face_bounds_small.left() - 10,
                                               face_bounds_small.top() - 20,
                                               face_bounds_small.right() + 10,
                                               face_bounds_small.bottom() + 20);
        tracker_small.start_track(example_small_dlib, padded_rectangle_small);

        opencv_tracker_roi_small = dlibRectangleToOpenCV(padded_rectangle_small);
        kcf_tracker_small->init(example_image, opencv_tracker_roi_small);
        medianflow_tracker_small->init(example_image, opencv_tracker_roi_small);
    }

    std::cout << "Size " << example_image.cols << "x" << example_image.rows << std::endl;
    std::cout << "Small size " << example_small_image.cols << "x" << example_small_image.rows << std::endl;
    std::cout << "Testing with " << TEST_ITERATIONS << " iterations, and " << TEST_SLOW_ITERATIONS << " iterations for slow ops" << std::endl;

    /*
     * Run benchmarks
     */
    timer(TEST_ITERATIONS, no_op, "Empty function");
    timer(TEST_ITERATIONS, resize_image, "Resize image");
    timer(TEST_ITERATIONS, resize_then_greyscale, "Resize then greyscale image");
    timer(TEST_ITERATIONS, greyscale_then_resize, "Greyscale then resize image");
    timer(TEST_ITERATIONS, blur_large, "Blur image (large)");
    timer(TEST_ITERATIONS, blur_small, "Blur image (small)");
    timer(TEST_ITERATIONS, frame_difference_large, "Frame difference (large)");
    timer(TEST_ITERATIONS, frame_difference_small, "Frame difference (small)");
    timer(TEST_ITERATIONS, threshold_large, "Threshold (large)");
    timer(TEST_ITERATIONS, threshold_small, "Threshold (small)");
    timer(TEST_ITERATIONS, dilate_large, "Dilate (large)");
    timer(TEST_ITERATIONS, dilate_small, "Dilate (small)");
    timer(TEST_ITERATIONS, erode_large, "Erode (large)");
    timer(TEST_ITERATIONS, erode_small, "Erode (small)");
    timer(TEST_ITERATIONS, find_contours_large, "Find contours (large)");
    timer(TEST_ITERATIONS, find_contours_small, "Find contours (small)");
    timer(TEST_ITERATIONS, norm2_large, "Norm2 (large)");
    timer(TEST_ITERATIONS, norm2_small, "Norm2 (small)");
    timer(TEST_ITERATIONS, convert_to_float_large, "Convert to float (large)");
    timer(TEST_ITERATIONS, convert_to_float_small, "Convert to float (small)");
    timer(TEST_ITERATIONS, accumulate_weighted_large, "Accumulate (large)");
    timer(TEST_ITERATIONS, accumulate_weighted_small, "Accumulate (small)");
    timer(TEST_ITERATIONS, bitwise_and_large, "Bitwise and (large)");
    timer(TEST_ITERATIONS, bitwise_and_small, "Bitwise and (small)");
    timer(TEST_ITERATIONS, sum_large, "Sum (large)");
    timer(TEST_ITERATIONS, sum_small, "Sum (small)");
    timer(TEST_ITERATIONS, convert_dlib_large, "Convert image to dlib (large)");
    timer(TEST_ITERATIONS, convert_dlib_small, "Convert image to dlib (small)");

    timer(TEST_ITERATIONS, face_landmarks_large, "Face landmarks (large)");
    if (do_small_face_tests) {
        timer(TEST_ITERATIONS, face_landmarks_small, "Face landmarks (small)");
    }
    timer(TEST_ITERATIONS, extract_face_chip_large, "Extract face chip (large)");
    if (do_small_face_tests) {
        timer(TEST_ITERATIONS, extract_face_chip_small, "Extract face chip (small)");
    }
    timer(TEST_ITERATIONS, compute_face_descriptor, "Face descriptor");

    /*
     * These only time a single frame update so they are not great overall tests of tracker
     * performance.
     */
    timer(TEST_ITERATIONS, correlation_tracker_update_large, "dlib correlation tracker update (large)");
    timer(TEST_ITERATIONS, opencv_kcf_tracker_update_large, "OpenCV KCF tracker update (large)");
    timer(TEST_ITERATIONS, opencv_medianflow_tracker_update_large, "OpenCV medianflow tracker update (large)");

    if (do_small_face_tests) {
        timer(TEST_ITERATIONS, correlation_tracker_update_small, "dlib correlation tracker update (small)");
        timer(TEST_ITERATIONS, opencv_kcf_tracker_update_small, "OpenCV KCF tracker update (small)");
        timer(TEST_ITERATIONS, opencv_medianflow_tracker_update_small, "OpenCV medianflow tracker update (small)");
    }

    // Slow operations
    timer(TEST_SLOW_ITERATIONS, detect_faces_large, "dlib detect faces (large)");
    timer(TEST_SLOW_ITERATIONS, detect_faces_small, "dlib detect faces (small)");
    timer(TEST_SLOW_ITERATIONS, detect_faces_opencv_large, "OpenCV detect faces (large)");
    timer(TEST_SLOW_ITERATIONS, detect_faces_opencv_small, "OpenCV detect faces (small)");

    return 0;
}


/*
 * This file is part of the statismo library.
 *
 * Author: Marcel Luethi (marcel.luethi@unibas.ch)
 *
 * Copyright (c) 2011 University of Basel
 * 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 project's author 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
 * HOLDER 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 "StatismoUnitTest.h"
#include "statismo/core/Exceptions.h"

#include "statismo/core/CommonTypes.h"
#include "statismo/core/DataManager.h"
#include "statismo/core/Domain.h"
#include "statismo/core/PosteriorModelBuilder.h"
#include "statismo/core/PCAModelBuilder.h"
#include "statismo/VTK/vtkStandardMeshRepresenter.h"
#include "vtkTestHelper.h"

#include <Eigen/Geometry>

#include <vtkMath.h>
#include <vtkVersion.h>

#include <memory>
#include <string>

using namespace statismo;
using namespace statismo::test;

namespace
{

using RepresenterType = vtkStandardMeshRepresenter;
using DataManagerType = statismo::BasicDataManager<vtkPolyData>;
using PointType = vtkStandardMeshRepresenter::PointType;
using DomainType = vtkStandardMeshRepresenter::DomainType;
using DomainPointsListType = DomainType::DomainPointsListType;
using StatisticalModelType = statismo::StatisticalModel<vtkPolyData>;

using PosteriorModelBuilderType = statismo::PosteriorModelBuilder<vtkPolyData>;
using PointValuePairType = PosteriorModelBuilderType::PointValuePairType;
using PointValueWithCovariancePairType = PosteriorModelBuilderType::PointValueWithCovariancePairType;
using PointValueWithCovarianceListType = PosteriorModelBuilderType::PointValueWithCovarianceListType;
using MatrixType = statismo::MatrixType;
using PCAModelBuilderType = statismo::PCAModelBuilder<vtkPolyData>;
std::vector<std::string> g_filenames;

int
TestPosteriorMain()
{

  const unsigned kNPointsFixed = 100;
  const unsigned kNPointsTest = 1000;
  const double   kTolerance = 0.01;
  const double   kPointValueNoiseVariance = 0.1;

  auto reference = LoadPolyData(g_filenames[0]);
  auto representer = RepresenterType::SafeCreate(reference);
  auto dataManager = DataManagerType::SafeCreate(representer.get());

  // dataManager->AddDataset(reference, "ref");
  for (const auto & f : g_filenames)
  {
    dataManager->AddDataset(LoadPolyData(f), "dataset");
  }

  const MatrixType                 kPointCovarianceMatrix = kPointValueNoiseVariance * MatrixType::Identity(3, 3);
  PointValueWithCovarianceListType pvcList;

  auto testSample = dataManager->GetData().back()->GetSample();

  auto domaintPointsList = representer->GetDomain().GetDomainPoints();
  auto nDomainPoints = representer->GetDomain().GetNumberOfPoints();

  for (std::size_t ptId = 0; ptId < nDomainPoints; ptId = ptId + nDomainPoints / kNPointsFixed)
  {
    pvcList.emplace_back(PointValuePairType(domaintPointsList[ptId], testSample->GetPoint(ptId)),
                         kPointCovarianceMatrix);
  }

  auto pModelBuilder = PosteriorModelBuilderType::SafeCreate();
  auto posteriorModel = pModelBuilder->BuildNewModel(dataManager->GetData(), pvcList, 0.1);

  auto posteriorMean = posteriorModel->DrawMean();

  for (std::size_t ptId = 0; ptId < nDomainPoints; ptId = ptId + nDomainPoints / kNPointsTest)
  {
    STATISMO_ASSERT_LTE(vtkMath::Distance2BetweenPoints(posteriorMean->GetPoint(ptId), testSample->GetPoint(ptId)),
                        kTolerance * kTolerance);
  }

  auto pcaModelBuilder = PCAModelBuilderType::SafeCreate();
  auto fullModel = pcaModelBuilder->BuildNewModel(dataManager->GetData(), 0.1, false);

  PointType middleFiducial(244, 290, 0);
  PointType funkyTargetPoint(270, 300, 0);

  statismo::VectorType anisotropicNoiseVariances(3);
  anisotropicNoiseVariances << .01, 100, .01; // 100, 100;

  // This assumes that the normal vectors is (1,0,0) and the tangential (0,1,0) and (0,0,1)
  MatrixType fiducialCovMatrix = anisotropicNoiseVariances.asDiagonal();

  PointValueWithCovarianceListType pointValueWithCovarianceList;
  pointValueWithCovarianceList.emplace_back(PointValuePairType{ middleFiducial, funkyTargetPoint }, fiducialCovMatrix);

  auto anisotropicPosteriorModelBuilder = PosteriorModelBuilderType::SafeCreate();
  auto anisotropicPosteriorModel =
    anisotropicPosteriorModelBuilder->BuildNewModelFromModel(fullModel.get(), pointValueWithCovarianceList, false);

  posteriorMean = anisotropicPosteriorModel->DrawMean();

  STATISMO_ASSERT_GT(fullModel->ComputeLogProbability(posteriorMean), -10.0);

  VectorType coeffs = fullModel->ComputeCoefficientsForPointValuesWithCovariance(pointValueWithCovarianceList);
  VectorType coeffsFromPosteriorMean = fullModel->ComputeCoefficients(posteriorMean);

  STATISMO_ASSERT_LTE((coeffs - coeffsFromPosteriorMean).norm(), kTolerance);

  return EXIT_SUCCESS;
}

int
TestPosteriorOnePoint()
{

  auto onePointPD = vtkSmartPointer<vtkPolyData>::New();
  auto onePoint = vtkSmartPointer<vtkPoints>::New();
  onePoint->Allocate(1);
  onePoint->InsertNextPoint(0, 0, 0);
  onePointPD->SetPoints(onePoint);
  auto       onePointRepresenter = RepresenterType::SafeCreate(onePointPD);
  VectorType onePointMean(3);
  onePointMean << 0, 0, 0;
  VectorType onePointVar(3);
  onePointVar << 1, 1, 1;
  MatrixType onePointPCABasis = MatrixType::Identity(3, 3);

  auto onePointModel =
    StatisticalModelType::SafeCreate(onePointRepresenter.get(), onePointMean, onePointPCABasis, onePointVar, 0.0);

  Eigen::Matrix3f rotMatrix;
  rotMatrix = Eigen::AngleAxisf(40, Eigen::Vector3f(0, 0, 1));

  VectorType covTestVector(3);
  covTestVector << 1, 0, 0;
  VectorType covTestVector1(3);
  covTestVector1 << 0, 2, 0;
  VectorType covTestVector2(3);
  covTestVector2 << 0, 0, 3;

  MatrixType testMatrix(3, 3);
  testMatrix.col(0) = covTestVector;
  testMatrix.col(1) = covTestVector1;
  testMatrix.col(2) = covTestVector2;

  testMatrix = rotMatrix * testMatrix;

  VectorType testValuePoint(3);
  testValuePoint << 1, 1, 1;
  testValuePoint = rotMatrix * testValuePoint;
  vtkPoint tvPoint(testValuePoint(0), testValuePoint(1), testValuePoint(2));

  MatrixType                       testCovMatrix = testMatrix * testMatrix.transpose();
  PointValuePairType               pvPair(vtkPoint(0, 0, 0), tvPoint);
  PointValueWithCovariancePairType pvcPair(pvPair, testCovMatrix);
  PointValueWithCovarianceListType onePointPvcList;
  onePointPvcList.push_back(pvcPair);

  auto onePointPosteriorModelBuilder = PosteriorModelBuilderType::SafeCreate();
  auto onePointPosteriorModel =
    onePointPosteriorModelBuilder->BuildNewModelFromModel(onePointModel.get(), onePointPvcList);

  VectorType posteriorModelMean = rotMatrix.inverse() * onePointPosteriorModel->GetMeanVector();
  VectorType knownMean(3);
  knownMean << 0.5, 0.2, 0.1;

  STATISMO_ASSERT_LTE((posteriorModelMean - knownMean).norm(), 0.00001);

  VectorType posteriorModelVariance = onePointPosteriorModel->GetPCAVarianceVector();
  VectorType knownVariance(3);
  knownVariance << 0.9, 0.8, 0.5;

  STATISMO_ASSERT_LTE((posteriorModelVariance - knownVariance).norm(), 0.00001);

  MatrixType rotatedOrthoPCAMatrix = rotMatrix.inverse() * onePointPosteriorModel->GetOrthonormalPCABasisMatrix();

  STATISMO_ASSERT_LTE((rotatedOrthoPCAMatrix * rotatedOrthoPCAMatrix.transpose() - MatrixType::Identity(3, 3)).norm(),
                      0.00001);

  return EXIT_SUCCESS;
}

} // namespace

int
PosteriorModelBuilderTest(int argc, char * argv[]) // NOLINT
{
  if (argc < 2)
  {
    std::cout << "Usage: " << argv[0] << " datadir" << std::endl;
    exit(EXIT_FAILURE);
  }

  std::string datadir = std::string(argv[1]);

  for (unsigned i = 0; i <= 4; ++i)
  {
    g_filenames.emplace_back(datadir + "/hand_polydata/hand-" + std::to_string(i) + ".vtk");
  }

  auto res = statismo::Translate([]() {
    return statismo::test::RunAllTests(
      "PosteriorModelBuilderTest",
      { { "TestPosteriorMain", TestPosteriorMain }, { "TestPosteriorOnePoint", TestPosteriorOnePoint } });
  });

  return !CheckResultAndAssert(res, EXIT_SUCCESS);
}


#ifndef PERIOR_TREE_RECTANGLE
#define PERIOR_TREE_RECTANGLE
#include "point_traits.hpp"
#include <boost/static_assert.hpp>
#include <boost/config.hpp>

namespace perior
{

template<typename pointT>
struct rectangle
{
    typedef pointT point_type;
    const static std::size_t dimension = traits::dimension<point_type>::value;
    BOOST_STATIC_ASSERT(traits::is_point<point_type>::value);

    rectangle(){}

    explicit rectangle(const point_type& r)
        : center(traits::zero_vector<point_type>()), radius(r)
    {}

    rectangle(const point_type& c, const point_type& r)
        : center(c), radius(r)
    {}

    rectangle(const rectangle& rhs)
        : center(rhs.center), radius(rhs.radius)
    {}
    rectangle& operator=(const rectangle& rhs)
    {
        center = rhs.center;
        radius = rhs.radius;
        return *this;
    }

    point_type center, radius;
};

template<typename pointT>
inline bool operator==(const rectangle<pointT>& lhs, const rectangle<pointT>& rhs)
    BOOST_NOEXCEPT_OR_NOTHROW
{
    return lhs.center == rhs.center && lhs.radius == rhs.radius;
}

template<typename pointT>
inline bool operator!=(const rectangle<pointT>& lhs, const rectangle<pointT>& rhs)
    BOOST_NOEXCEPT_OR_NOTHROW
{
    return lhs.center != rhs.center || lhs.radius != rhs.radius;
}

template<typename pointT, typename charT, typename traits>
std::basic_ostream<charT, traits>&
operator<<(std::basic_ostream<charT, traits>& os, const rectangle<pointT>& rect)
{
    os << "rectangle {c = " << rect.center << ", r = " << rect.radius << '}';
    return os;
}

} // perior
#endif// PERIOR_TREE_RECTANGLE


#include "ising_system.h"
#include "mc_sampler.h"

// C++ Standard Library
#include <iomanip>

// Boost Bind
#include <boost/bind.hpp>

// Boost Format
#include <boost/format.hpp>

namespace po = boost::program_options;

/**
 * Private Methods:
 */

void IsingSystem::set_size(size_t L) {
	size = L;
	lattice.resize(L, L);
	e_min = -2 * L * L;
	//e_max =  2 * L * L;
	e_max =  0;
	n_bins = (e_max - e_min)/4 + 1;
	Q.resize(n_bins, n_bins);
	Qd.resize(n_bins, n_bins);

	std::fill(Q.data().begin(), Q.data().end(), 0);
	std::fill(Qd.data().begin(), Qd.data().end(), 0.0);
}

int IsingSystem::reset() {
	int magnetization = 0;
	// set lattice to random state
	for (size_t i = 0; i < lattice.data().size(); ++i) {
		lattice.data()[i] = (dist01(rng.rng) > 0.5 ? 1 : -1);
		magnetization += lattice.data()[i];
	}
	return magnetization;
}

int IsingSystem::calculate_energy() {
	int energy = 0;
	for (size_t i = 0; i < size; ++i) {
		for (size_t j = 0; j < size; ++j) {
			energy += -J * (lattice(i, j) * (
						lattice((i-1+size)%size,  j              ) +
						lattice( i             , (j-1+size)%size )
						));
		}
	}
	return energy;
}

void IsingSystem::read_exact_dos() {
	std::string format = "%1%/data/ising/exact_%2%.dat";
	std::string fn = str( boost::format(format)
			% boost::any_cast<std::string>(settings["executable_path"])
			% size
			);
	std::ifstream in(fn.c_str());

	dos_exact_norm.resize(n_bins);
	dos_exact_norm *= 0;
	for (size_t i = 0; i < n_bins; i++) {
		int energy;
		double dos;
		in >> energy >> dos;
		dos_exact_norm[i] = dos;
	}
}

void IsingSystem::setup_output() {
	std::vector<std::string> sampler_string;
	// initialize list of available samplers
	sampler_string.push_back("BM");
	sampler_string.push_back("WL");
	sampler_string.push_back("QB");
	sampler_string.push_back("QA");
	sampler_string.push_back("TM");
	sampler_string.push_back("1t");

	std::string format;
	switch (sampler) {
		case 1:
			format = "ising_%1%_%2%S_%3%R_%4%M_%6$0.2ff%7%%8%.out";
			break;
		case 2:
		case 3:
		case 4:
		case 5:
			format = "ising_%1%_%2%S_%3%R_%4%M%7%%8%.out";
			break;
		case 0:
			format = "ising_%1%_%2%S_%3%R_%4%M_%5$0.2fT_%9%Sd%7%%8%.out";
			break;
		default:
			throw std::runtime_error("Unknown smapler");
	}
	boost::format fmt(format);
	fmt.exceptions(boost::io::all_error_bits ^ (
				boost::io::too_many_args_bit | boost::io::too_few_args_bit
				));
	fn_template = str( fmt
			% sampler_string[sampler]
			% steps
			% "%1%"
			% n_bins
			% boost::any_cast<double>(settings["temperature"])
			% boost::any_cast<double>(settings["flatness"])
			% (tag.size() > 0 ? "_" : "")
			% tag
			% boost::any_cast<size_t>(settings["schedule"])
		);

	out << "#";
	out << "\n# sampler:      " << sampler << " - " << sampler_string[sampler];
	out << "\n# steps:        " << steps;
	out << "\n# n_bins:       " << n_bins;
	out << "\n# temperature:  " << boost::any_cast<double>(settings["temperature"]);
	out << "\n# flatness:     " << boost::any_cast<double>(settings["flatness"]);
	out << "\n# one-over-t-c: " << boost::any_cast<double>(settings["one-over-t-c"]);
	out << "\n# one-over-t-s: " << boost::any_cast<size_t>(settings["one-over-t-s"]);
}

/**
 * Public Methods
 */

/**
 * Constructor
 */
IsingSystem::IsingSystem(settings_t &s) : SimulationSystem(s) {}

po::options_description IsingSystem::get_program_options() {
	po::options_description desc("Ising System Options");
	desc.add_options()
		(
			"ising-size",
			po::value<size_t>(&size)->default_value(14)->notifier(boost::bind(&IsingSystem::set_size, this, _1)),
			"Number of Spins in one dimension.\nGrid will be size*size"
		) (
			"ising-interaction",
			po::value<double>(&J)->default_value(1),
			"Interaction J\nJ>0 - ferromagnetic\nJ<0 - antiferromagnetic\nJ=0 - noninteracting"
		);
	return desc;
}

void IsingSystem::parse_arguments(boost::program_options::variables_map &) {}

void IsingSystem::setup() {
	SimulationSystem::setup();

	try {
		sampler       = boost::any_cast<size_t>(settings["sampler"]);
	} catch(const boost::bad_any_cast &e) {
		std::cerr << "Error while extracting settings in "
			<< __FILE__ << ":" << __LINE__ << "\nExecption: "
			<< e.what() << std::endl;
		throw e;
	}

	// set_size should have been called at this point
	// and thus n_bins should have a value
	settings["macrostates"] = n_bins;
	size_t num_unvisited_energies = 1;
	settings["num_unvisited_energies"] = num_unvisited_energies;

	read_exact_dos();
	setup_output();
}

bool IsingSystem::run() {
	switch (sampler) {
	case 0:
		mc_loop<BoltzmannSampler>();
		break;
	case 1:
		mc_loop<WangLandauSampler>();
		break;
	case 2:
		mc_loop<QualityMeasureBSampler>();
		break;
	case 3:
		mc_loop<QualityMeasureASampler>();
		break;
	case 4:
		mc_loop<TransitionMatrixSampler>();
		break;
	case 5:
		mc_loop<WangLandau1tSampler>();
		break;
	default:
		std::cerr << "Error: unknown sampler" << std::endl;
		return false;
	}

	return true;
}


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS
  @copyright 2016 J.T. Lapreste

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_FUNCTION_SPLATTED_MAXIMUM_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_SPLATTED_MAXIMUM_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{

 /*!

    @ingroup group-swar
    Function object implementing splatted_maximum capabilities

    Splatted maximum

    Computes the splatted maximum of the element of its argument.

    @par Semantic

    Depending on the type of its arguments, splatted_maximum exhibits different
    semantics. For any type @c T and value @c x of type @c T:

    @code
    T r = splatted_maximum(x);
    @endcode

    is similar to:

    @code
    for(int i=0;i<T::static_size;++i) x[i] = maximum(x);
    @endcode

  **/
  const boost::dispatch::functor<tag::splatted_maximum_> splatted_maximum = {};
} }
#endif

#include <boost/simd/function/scalar/splatted_maximum.hpp>
#include <boost/simd/function/simd/splatted_maximum.hpp>

#endif


#ifndef __SOLVER__
#define __SOLVER__

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

namespace proxopp
{
class Solver
{
public:
	Solver(int n, int verbose=1, int max_steps=100, std::string name="solver") : step(0), verbose(verbose),
			max_steps(max_steps), name(name)
	{
		this->n = n;
		_x = Eigen::VectorXf::Zero(n);

		if(verbose > 1)
		{
			output.open("output/" + name + ".csv");

			if(!output.is_open())
			{
				std::cout << "(" << name << ")" << " cannot open output file\n";
			} else
			{
				output << "step,objective\n";
			}
		}

		stop = 0;
	}

	virtual ~Solver()
	{
		if(output.is_open()) output.close();
	}

	virtual void callback()
	{
		if(verbose > 0)
		{
			std::cout << "(" << name << ") " << step << "/" << max_steps << " objective: " << currentObjective() << " \n";

			if(verbose > 1 && output.is_open())
			{
				output << step << "," << currentObjective() << std::endl;
			}
		}
	}

	virtual void iterate() {}

	virtual float currentObjective()
	{
		return 0.0f;
	}

	virtual void initParameters(Eigen::MatrixXf& A, Eigen::VectorXf& b) {}

	virtual Eigen::VectorXf solve(Eigen::MatrixXf& A, Eigen::VectorXf& b)
	{
		initParameters(A,b);

		for(step = 0; step < max_steps; step++)
		{
			iterate();
			callback();
			if(stop) break;
		}

		return _x;
	}

	void setMaxSteps(int max_steps)
	{
		this->max_steps = max_steps;
	}

	void setVerbose(int verbose)
	{
		this->verbose = verbose;
	}

	int getMaxSteps() { return max_steps; }

	std::string getName() { return name; }

protected:
	int verbose;
	int n;
	int step;
	int stop;
	int max_steps;
	std::string name;
	std::ofstream output;

	Eigen::VectorXf _x; // current state
};
} // namespace proxopp

#endif


/*
 * Software License Agreement (BSD License)
 *
 *  Copyright (c) 2014 Max-Planck-Institute for Intelligent Systems,
 *                     University of Southern California
 *    Jan Issac (jan.issac@gmail.com)
 *    Manuel Wuthrich (manuel.wuthrich@gmail.com)
 *
 *
 *
 *  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 Willow Garage, Inc. 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.
 *
 */

/**
 * \file joint_distribution_id_test.cpp
 * \date Febuary 2015
 * \author Jan Issac (jan.issac@gmail.com)
 */

#include <gtest/gtest.h>

#include <Eigen/Dense>

#include <cmath>
#include <iostream>

#include <fl/distribution/gaussian.hpp>
#include <fl/distribution/joint_distribution_id.hpp>

TEST(JointDistribution_ID_Tests, fixed_accessing_marginals)
{
    typedef Eigen::Matrix<double, 2, 1> FVariate;
    typedef fl::JointDistribution<fl::Gaussian<FVariate>> FSingeDistribution;

    FSingeDistribution joint_distr =
        FSingeDistribution(fl::Gaussian<FVariate>());

    typedef fl::Traits<
                std::tuple_element<
                    0,
                    fl::Traits<FSingeDistribution>::MarginalDistributions
                >::type
            >::Variate ExpectedMean;

    typedef fl::Traits<
                std::tuple_element<
                    0,
                    fl::Traits<FSingeDistribution>::MarginalDistributions
                >::type
            >::SecondMoment ExpectedCovariance;

    EXPECT_EQ(joint_distr.dimension(), 2);
    EXPECT_TRUE(fl::are_similar(joint_distr.mean(), ExpectedMean::Zero()));
    EXPECT_TRUE(fl::are_similar(joint_distr.covariance(),
                                ExpectedCovariance::Identity()));

    EXPECT_TRUE(fl::are_similar(
                    std::get<0>(joint_distr.distributions()).mean(),
                    ExpectedMean::Zero()));

    EXPECT_TRUE(fl::are_similar(
                    std::get<0>(joint_distr.distributions()).covariance(),
                    ExpectedCovariance::Identity()));
}

TEST(JointDistribution_ID_Tests, fixed_single_gaussian)
{
    typedef Eigen::Matrix<double, 2, 1> FVariate;
    typedef fl::JointDistribution<fl::Gaussian<FVariate>> FSingeDistribution;

    FSingeDistribution joint_distr =
        FSingeDistribution(fl::Gaussian<FVariate>());

    constexpr int dim = 2;

    typedef Eigen::Matrix<double, dim, 1> ExpectedMean;
    typedef Eigen::Matrix<double, dim, dim> ExpectedCovariance;

    EXPECT_EQ(joint_distr.dimension(), 2);
    EXPECT_TRUE(fl::are_similar(joint_distr.mean(), ExpectedMean::Zero()));
    EXPECT_TRUE(fl::are_similar(joint_distr.covariance(),
                                ExpectedCovariance::Identity()));

    EXPECT_TRUE(fl::are_similar(
                    std::get<0>(joint_distr.distributions()).mean(),
                    ExpectedMean::Zero()));

    EXPECT_TRUE(fl::are_similar(
                    std::get<0>(joint_distr.distributions()).covariance(),
                    ExpectedCovariance::Identity()));
}

TEST(JointDistribution_ID_Tests, fixed_two_gaussians)
{
    typedef Eigen::Matrix<double, 2, 1> FVariate2D;
    typedef Eigen::Matrix<double, 7, 1> FVariate7D;
    typedef fl::JointDistribution<
                fl::Gaussian<FVariate2D>,
                fl::Gaussian<FVariate7D>> FJointDistribution;

    FJointDistribution joint_distr =
        FJointDistribution(
            fl::Gaussian<FVariate2D>(),
            fl::Gaussian<FVariate7D>());

    constexpr int dim = 2 + 7;

    typedef Eigen::Matrix<double, dim, 1> ExpectedMean;
    typedef Eigen::Matrix<double, dim, dim> ExpectedCovariance;

    EXPECT_EQ(joint_distr.dimension(), dim);
    EXPECT_TRUE(fl::are_similar(joint_distr.mean(), ExpectedMean::Zero()));
    EXPECT_TRUE(fl::are_similar(joint_distr.covariance(),
                                ExpectedCovariance::Identity()));
}

TEST(JointDistribution_ID_Tests, fixed_three_gaussians)
{
    typedef Eigen::Matrix<double, 2, 1> FVariate2D;
    typedef Eigen::Matrix<double, 7, 1> FVariate7D;
    typedef Eigen::Matrix<double, 13, 1> FVariate13D;

    typedef fl::JointDistribution<
                fl::Gaussian<FVariate2D>,
                fl::Gaussian<FVariate13D>,
                fl::Gaussian<FVariate7D>> FJointDistribution;

    FJointDistribution joint_distr =
        FJointDistribution(
            fl::Gaussian<FVariate2D>(),
            fl::Gaussian<FVariate13D>(),
            fl::Gaussian<FVariate7D>());

    constexpr int dim = 2 + 13 + 7;

    typedef Eigen::Matrix<double, dim, 1> ExpectedMean;
    typedef Eigen::Matrix<double, dim, dim> ExpectedCovariance;

    EXPECT_EQ(joint_distr.dimension(), dim);
    EXPECT_TRUE(fl::are_similar(joint_distr.mean(), ExpectedMean::Zero()));
    EXPECT_TRUE(fl::are_similar(joint_distr.covariance(),
                                ExpectedCovariance::Identity()));
}

TEST(JointDistribution_ID_Tests, fixed_recursive_definition)
{
    typedef Eigen::Matrix<double, 2, 1> FVariate2D;
    typedef Eigen::Matrix<double, 7, 1> FVariate7D;

    typedef fl::JointDistribution<
                fl::Gaussian<FVariate7D>
            > InnerJointDistribution;

    typedef fl::JointDistribution<
                fl::Gaussian<FVariate2D>,
                InnerJointDistribution
            > FJointDistribution;

    FJointDistribution joint_distr =
        FJointDistribution(
            fl::Gaussian<FVariate2D>(),
            InnerJointDistribution(fl::Gaussian<FVariate7D>()));

    constexpr int dim = 2 + 7;

    typedef Eigen::Matrix<double, dim, 1> ExpectedMean;
    typedef Eigen::Matrix<double, dim, dim> ExpectedCovariance;

    EXPECT_EQ(joint_distr.dimension(), dim);
    EXPECT_TRUE(fl::are_similar(joint_distr.mean(), ExpectedMean::Zero()));
    EXPECT_TRUE(fl::are_similar(joint_distr.covariance(),
                                ExpectedCovariance::Identity()));
}

TEST(JointDistribution_ID_Tests, fixed_deep_recursive_definition)
{
    typedef Eigen::Matrix<double, 2, 1> FVariate2D;
    typedef Eigen::Matrix<double, 7, 1> FVariate7D;

    typedef fl::JointDistribution<
                fl::Gaussian<FVariate7D>
            > InnerInnerJointDistribution;

    typedef fl::JointDistribution<
                InnerInnerJointDistribution,
                InnerInnerJointDistribution
            > InnerJointDistribution;

    typedef fl::JointDistribution<
                fl::Gaussian<FVariate2D>,
                InnerJointDistribution
            > FJointDistribution;

    FJointDistribution joint_distr =
        FJointDistribution(
            fl::Gaussian<FVariate2D>(),
            InnerJointDistribution(
                InnerInnerJointDistribution(fl::Gaussian<FVariate7D>()),
                InnerInnerJointDistribution(fl::Gaussian<FVariate7D>())));

    constexpr int dim = 2 + 2 * 7;

    typedef Eigen::Matrix<double, dim, 1> ExpectedMean;
    typedef Eigen::Matrix<double, dim, dim> ExpectedCovariance;

    EXPECT_EQ(joint_distr.dimension(), dim);
    EXPECT_TRUE(fl::are_similar(joint_distr.mean(), ExpectedMean::Zero()));
    EXPECT_TRUE(fl::are_similar(joint_distr.covariance(),
                                ExpectedCovariance::Identity()));
}

TEST(JointDistribution_ID_Tests, dynamic_single_gaussian)
{
    constexpr int dim = 2;

    typedef Eigen::Matrix<double, -1, 1> DVariate;
    typedef fl::JointDistribution<fl::Gaussian<DVariate>> DSingeDistribution;

    DSingeDistribution joint_distr =
        DSingeDistribution(fl::Gaussian<DVariate>(dim));

    typedef Eigen::Matrix<double, dim, 1> ExpectedMean;
    typedef Eigen::Matrix<double, dim, dim> ExpectedCovariance;

    EXPECT_EQ(joint_distr.dimension(), 2);

    EXPECT_TRUE(fl::are_similar(joint_distr.mean(), ExpectedMean::Zero(dim, 1)));
    EXPECT_TRUE(fl::are_similar(joint_distr.covariance(),
                                ExpectedCovariance::Identity(dim, dim)));

    EXPECT_TRUE(fl::are_similar(
                    std::get<0>(joint_distr.distributions()).mean(),
                    ExpectedMean::Zero(dim, 1)));

    EXPECT_TRUE(fl::are_similar(
                    std::get<0>(joint_distr.distributions()).covariance(),
                    ExpectedCovariance::Identity(dim , dim)));
}

TEST(JointDistribution_ID_Tests, dynamic_three_gaussians)
{
    typedef Eigen::Matrix<double, -1, 1> DVariate;

    constexpr int dim2 = 2;
    constexpr int dim13 = 13;
    constexpr int dim7 =  7;

    constexpr int dim =  dim2 + dim13 + dim7;

    typedef fl::JointDistribution<
                fl::Gaussian<DVariate>,
                fl::Gaussian<DVariate>,
                fl::Gaussian<DVariate>> FJointDistribution;

    FJointDistribution joint_distr =
        FJointDistribution(
            fl::Gaussian<DVariate>(dim2),
            fl::Gaussian<DVariate>(dim13),
            fl::Gaussian<DVariate>(dim7));

    typedef Eigen::Matrix<double, dim, 1> ExpectedMean;
    typedef Eigen::Matrix<double, dim, dim> ExpectedCovariance;

    EXPECT_EQ(joint_distr.dimension(), dim);
    EXPECT_TRUE(fl::are_similar(joint_distr.mean(), ExpectedMean::Zero(dim, 1)));
    EXPECT_TRUE(fl::are_similar(joint_distr.covariance(),
                                ExpectedCovariance::Identity(dim, dim)));
}


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef BOOST_SIMD_TOOLBOX_IEEE_FUNCTIONS_SCALAR_MODF_HPP_INCLUDED
#define BOOST_SIMD_TOOLBOX_IEEE_FUNCTIONS_SCALAR_MODF_HPP_INCLUDED
#include <boost/simd/toolbox/ieee/functions/modf.hpp>
#include <boost/fusion/include/at.hpp>
#include <boost/fusion/include/vector.hpp>
#include <boost/simd/include/functions/scalar/trunc.hpp>

namespace boost { namespace simd { namespace ext
{
  BOOST_SIMD_FUNCTOR_IMPLEMENTATION(boost::simd::tag::modf_, tag::cpu_,
                      (A0),
                      (scalar_ < arithmetic_<A0> > )
                      (scalar_ < arithmetic_<A0> > )
                      (scalar_ < arithmetic_<A0> > )
                     )
  {
    typedef int result_type;
    inline int32_t operator()(A0 const& a0,A0 & r1,A0 & r0) const
    {
      r1 = boost::simd::trunc(a0);
      r0 = a0 - r1;
      return 0;
    }
  };

  BOOST_SIMD_FUNCTOR_IMPLEMENTATION(boost::simd::tag::modf_, tag::cpu_,
                      (A0),
                      (scalar_ < arithmetic_<A0> > )
                      (scalar_ < arithmetic_<A0> > )
                     )
  {
    typedef A0 result_type;
    inline A0 operator()(A0 const& a0,A0 & a1) const
    {
      a1 = boost::simd::trunc(a0);
      return a0 - a1;
    }
  };

  BOOST_SIMD_FUNCTOR_IMPLEMENTATION(boost::simd::tag::modf_, tag::cpu_,
                      (A0),
                      (scalar_ < arithmetic_<A0> > )
                     )
  {
    typedef boost::fusion::vector<A0,A0> result_type;
    BOOST_SIMD_FUNCTOR_CALL(1)
    {
      result_type res;
      boost::simd::modf(a0, boost::fusion::at_c<1>(res), boost::fusion::at_c<0>(res));
      return res;
    }
  };
} } }
#endif


#include <boost/test/unit_test.hpp>

#include <scorum/app/api_context.hpp>

#include <scorum/app/database_api.hpp>

#include <scorum/chain/services/account.hpp>

#include <scorum/chain/schema/account_objects.hpp>

#include <scorum/rewards_math/formulas.hpp>

#include "database_trx_integration.hpp"
#include "database_blog_integration.hpp"

using namespace scorum;
using namespace scorum::chain;
using namespace scorum::protocol;
using namespace scorum::app;
using namespace scorum::rewards_math;
using fc::string;

namespace account_api_tests {

using namespace database_fixture;

struct account_api_test_fixture : public database_blog_integration_fixture
{
    account_api_test_fixture()
        : _database_api_ctx(app, API_DATABASE, std::make_shared<api_session_data>())
        , database_api_call(_database_api_ctx)
        , account_service(db.account_service())
    {
        open_database();

        actor(initdelegate).create_account(alice);
        actor(initdelegate).create_account(sam);

        actor(initdelegate).give_sp(alice, 1e9);
        actor(initdelegate).give_sp(sam, 1e9);
    }

    api_context _database_api_ctx;
    database_api database_api_call;
    account_service_i& account_service;

    Actor alice = "alice";
    Actor sam = "sam";
};

BOOST_FIXTURE_TEST_SUITE(database_api_account_tests, account_api_test_fixture)

SCORUM_TEST_CASE(check_voting_power)
{
    auto vote_interval = SCORUM_CASHOUT_WINDOW_SECONDS / 2;

    auto alice_post = create_post(alice).in_block(vote_interval);
    alice_post.vote(sam).in_block();

    auto sam_voting_power = account_service.get_account(sam.name).voting_power;

    generate_blocks(db.head_block_time() + SCORUM_CASHOUT_WINDOW_SECONDS - vote_interval);

    BOOST_REQUIRE_EQUAL(account_service.get_account(sam.name).voting_power, sam_voting_power);

    auto api_objs = database_api_call.get_accounts({ sam.name });

    BOOST_REQUIRE_EQUAL(api_objs.size(), 1u);

    extended_account api_obj = api_objs[0];

    auto predicted_voting_power = calculate_restoring_power(
        account_service.get_account(sam.name).voting_power, db.head_block_time(),
        account_service.get_account(sam.name).last_vote_time, SCORUM_VOTE_REGENERATION_SECONDS);

    BOOST_REQUIRE_EQUAL(api_obj.voting_power, predicted_voting_power);
}

BOOST_AUTO_TEST_SUITE_END()
}


#pragma once
#include <Eigen/Dense>
#include <iostream>
#include <random>
#include <vector>

#include "multi_tree_view.hpp"

namespace datastructures {

// Helper functions for converting between (flattened) trees and vectors.
template <typename Iterable>
Eigen::VectorXd ToVector(const Iterable &nodes) {
  Eigen::VectorXd result(nodes.size());
  size_t i = 0;
  for (const auto &node : nodes) {
    if constexpr (std::is_pointer_v<typename Iterable::value_type>)
      result[i++] = node->value();
    else
      result[i++] = node.value();
  }
  return result;
}
template <typename Iterable>
void FromVector(const Iterable &nodes, const Eigen::VectorXd &vec) {
  assert(nodes.size() == vec.size());
  size_t i = 0;
  for (auto &node : const_cast<Iterable &>(nodes)) {
    if constexpr (std::is_pointer_v<typename Iterable::value_type>)
      node->set_value(vec[i++]);
    else
      node.set_value(vec[i++]);
  }
}

template <typename I>
class MultiNodeVectorInterface {
 public:
  inline I *self() { return static_cast<I *>(this); }
  inline const I *self() const { return static_cast<const I *>(this); }

  void Reset() {
    for (const auto &node : self()->Bfs(true)) node->set_value(0);
  }

  // Note: this is not compatible with the Python ToArray!
  Eigen::VectorXd ToVector() const {
    return datastructures::ToVector(const_cast<I *>(self())->Bfs());
  }
  void FromVector(const Eigen::VectorXd &vec) {
    datastructures::FromVector(self()->Bfs(), vec);
  }
};

template <typename I, typename... T>
class MultiNodeVectorBase : public MultiNodeViewBase<I, T...>,
                            public MultiNodeVectorInterface<I> {
 public:
  using MultiNodeViewBase<I, T...>::MultiNodeViewBase;

  inline const double &value() const { return value_; }
  inline void set_value(double val) {
    // assert(val == 0.0 || (1e-100 < std::abs(val) && std::abs(val) < 1e100));
    value_ = val;
  }
  inline void set_random() {
    value_ = static_cast<double>(std::rand()) / RAND_MAX;
  }

 protected:
  double value_ = 0.0;
};

// Create instance of this class
template <typename... T>
class MultiNodeVector
    : public MultiNodeVectorBase<MultiNodeVector<T...>, T...> {
 public:
  using MultiNodeVectorBase<MultiNodeVector<T...>, T...>::MultiNodeVectorBase;
};

template <typename I>
class MultiTreeVector : public MultiTreeView<I> {
 private:
  using Super = MultiTreeView<I>;

 public:
  using MultiTreeView<I>::MultiTreeView;

  // Note: this is not compatible with the Python ToArray!
  Eigen::VectorXd ToVector() const { return Super::root_->ToVector(); }
  void FromVector(const Eigen::VectorXd &vec) { Super::root_->FromVector(vec); }
  void FromVector(const MultiTreeVector<I> &vec) {
    Reset();
    Super::root_->Union(vec.root(),
                        /* call_filter*/ func_true, /* call_postprocess*/
                        [](const auto &my_node, const auto &other_node) {
                          my_node->set_value(other_node->value());
                        });
  }

  // This uses the ordering as in the underlying container.
  Eigen::VectorXd ToVectorContainer() const {
    return datastructures::ToVector(Super::multi_nodes_);
  }
  void FromVectorContainer(const Eigen::VectorXd &vec) {
    datastructures::FromVector(Super::multi_nodes_, vec);
  }

  // Create a deepcopy that copies the vector data as well.
  template <typename MT_other = MultiTreeVector<I>>
  MT_other DeepCopy() const {
    return Super::template DeepCopy<MT_other>(
        [&](const auto &new_node, const auto &old_node) {
          new_node->set_value(old_node->value());
        });
  }

  // Set all values to zero.
  void Reset() {
    for (auto &node : Super::multi_nodes_) node.set_value(0);
  }

  // Define some simple linear algebra functions.
  MultiTreeVector<I> &operator*=(double val) {
    for (auto &nv : Super::multi_nodes_) {
      nv.set_value(nv.value() * val);
    }
    return *this;
  }
  template <typename MT_other = MultiTreeVector<I>>
  MultiTreeVector<I> &operator+=(const MT_other &rhs) {
    Super::root_->Union(
        rhs.root(), /* call_filter*/ func_true, /* call_postprocess*/
        [](const auto &my_node, const auto &other_node) {
          my_node->set_value(my_node->value() + other_node->value());
        });
    return *this;
  }

  friend std::ostream &operator<<(std::ostream &os,
                                  const MultiTreeVector<I> &tree) {
    os << "{";
    for (auto mnv : tree.Bfs()) os << *mnv << " ";
    os << "}";
    return os;
  }
};

template <typename T0>
using NodeVector = MultiNodeVector<T0>;

template <typename T0>
using TreeVector = MultiTreeVector<MultiNodeVector<T0>>;

template <typename T0, typename T1, typename T2>
using TripleTreeVector = MultiTreeVector<MultiNodeVector<T0, T1, T2>>;

}  // namespace datastructures


/*
 Copyright (C) 2018 Quaternion Risk Management Ltd
 All rights reserved.

 This file is part of ORE, a free-software/open-source library
 for transparent pricing and risk analysis - http://opensourcerisk.org

 ORE is free software: you can redistribute it and/or modify it
 under the terms of the Modified BSD License.  You should have received a
 copy of the license along with this program.
 The license is also available online at <http://opensourcerisk.org>

 This program is distributed on the basis that it will form a useful
 contribution to risk analytics and model standardisation, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
*/

#include <boost/test/unit_test.hpp>
#include <oret/toplevelfixture.hpp>

#include <boost/make_shared.hpp>

#include <ored/configuration/curveconfigurations.hpp>
#include <ored/marketdata/commodityvolcurve.hpp>
#include <ored/marketdata/curvespec.hpp>
#include <ored/marketdata/loader.hpp>

using namespace std;
using namespace boost::unit_test_framework;
using namespace QuantLib;
using namespace QuantExt;
using namespace ore::data;

namespace {

// testTolerance for Real comparison
Real testTolerance = 1e-10;

class MockLoader : public Loader {
public:
    MockLoader();
    const vector<boost::shared_ptr<MarketDatum>>& loadQuotes(const Date&) const { return data_; }
    const boost::shared_ptr<MarketDatum>& get(const string& name, const Date&) const { return dummyDatum_; }
    const vector<Fixing>& loadFixings() const { return dummyFixings_; }
    const vector<Fixing>& loadDividends() const { return dummyDividends_; }

private:
    vector<boost::shared_ptr<MarketDatum>> data_;
    boost::shared_ptr<MarketDatum> dummyDatum_;
    vector<Fixing> dummyFixings_;
    vector<Fixing> dummyDividends_;
};

MockLoader::MockLoader() {
    Date asof(5, Feb, 2016);
    data_ = {
        boost::make_shared<CommodityOptionQuote>(0.11, asof, "COMMODITY_OPTION/RATE_LNVOL/GOLD/USD/1Y/ATMF",
                                                 MarketDatum::QuoteType::RATE_LNVOL, "GOLD", "USD", "1Y", "ATMF"),
        boost::make_shared<CommodityOptionQuote>(0.10, asof, "COMMODITY_OPTION/RATE_LNVOL/GOLD/USD/2Y/ATMF",
                                                 MarketDatum::QuoteType::RATE_LNVOL, "GOLD", "USD", "2Y", "ATMF"),
        boost::make_shared<CommodityOptionQuote>(0.09, asof, "COMMODITY_OPTION/RATE_LNVOL/GOLD/USD/5Y/ATMF",
                                                 MarketDatum::QuoteType::RATE_LNVOL, "GOLD", "USD", "5Y", "ATMF"),
        boost::make_shared<CommodityOptionQuote>(0.105, asof, "COMMODITY_OPTION/RATE_LNVOL/GOLD_USD_VOLS/USD/1Y/1150",
                                                 MarketDatum::QuoteType::RATE_LNVOL, "GOLD", "USD", "1Y", "1150"),
        boost::make_shared<CommodityOptionQuote>(0.115, asof, "COMMODITY_OPTION/RATE_LNVOL/GOLD_USD_VOLS/USD/1Y/1190",
                                                 MarketDatum::QuoteType::RATE_LNVOL, "GOLD", "USD", "1Y", "1190"),
        boost::make_shared<CommodityOptionQuote>(0.095, asof, "COMMODITY_OPTION/RATE_LNVOL/GOLD_USD_VOLS/USD/2Y/1150",
                                                 MarketDatum::QuoteType::RATE_LNVOL, "GOLD", "USD", "2Y", "1150"),
        boost::make_shared<CommodityOptionQuote>(0.105, asof, "COMMODITY_OPTION/RATE_LNVOL/GOLD_USD_VOLS/USD/2Y/1190",
                                                 MarketDatum::QuoteType::RATE_LNVOL, "GOLD", "USD", "2Y", "1190"),
        boost::make_shared<CommodityOptionQuote>(0.085, asof, "COMMODITY_OPTION/RATE_LNVOL/GOLD_USD_VOLS/USD/5Y/1150",
                                                 MarketDatum::QuoteType::RATE_LNVOL, "GOLD", "USD", "5Y", "1150"),
        boost::make_shared<CommodityOptionQuote>(0.095, asof, "COMMODITY_OPTION/RATE_LNVOL/GOLD_USD_VOLS/USD/5Y/1190",
                                                 MarketDatum::QuoteType::RATE_LNVOL, "GOLD", "USD", "5Y", "1190")};
}

} // namespace

BOOST_FIXTURE_TEST_SUITE(OREDataTestSuite, ore::test::TopLevelFixture)

BOOST_AUTO_TEST_SUITE(CommodityVolCurveTests)

BOOST_AUTO_TEST_CASE(testCommodityVolCurveTypeConstant) {

    BOOST_TEST_MESSAGE("Testing commodity vol curve building with a single configured volatility");

    // As of date
    Date asof(5, Feb, 2016);

    // Volatility configuration with a single quote
    boost::shared_ptr<CommodityVolatilityCurveConfig> curveConfig = boost::make_shared<CommodityVolatilityCurveConfig>(
        "GOLD_USD_VOLS", "", "USD", "COMMODITY_OPTION/RATE_LNVOL/GOLD/USD/2Y/ATMF", "A365", "NullCalendar");

    // Curve configurations
    CurveConfigurations curveConfigs;
    curveConfigs.commodityVolatilityCurveConfig("GOLD_USD_VOLS") = curveConfig;

    // Commodity curve spec
    CommodityVolatilityCurveSpec curveSpec("USD", "GOLD_USD_VOLS");

    // Market data loader
    MockLoader loader;

    // Check commodity volatility construction works
    boost::shared_ptr<CommodityVolCurve> curve;
    BOOST_CHECK_NO_THROW(curve = boost::make_shared<CommodityVolCurve>(asof, curveSpec, loader, curveConfigs));

    // Check volatilities are all equal to the configured volatility regardless of strike and expiry
    Real configuredVolatility = 0.10;
    boost::shared_ptr<BlackVolTermStructure> volatility = curve->volatility();
    BOOST_CHECK_CLOSE(volatility->blackVol(0.25, 1000.0), configuredVolatility, testTolerance);
    BOOST_CHECK_CLOSE(volatility->blackVol(0.25, 1200.0), configuredVolatility, testTolerance);
    BOOST_CHECK_CLOSE(volatility->blackVol(asof + 3 * Months, 1000.0), configuredVolatility, testTolerance);
    BOOST_CHECK_CLOSE(volatility->blackVol(asof + 3 * Months, 1200.0), configuredVolatility, testTolerance);
    BOOST_CHECK_CLOSE(volatility->blackVol(50.0, 1000.0), configuredVolatility, testTolerance);
    BOOST_CHECK_CLOSE(volatility->blackVol(50.0, 1200.0), configuredVolatility, testTolerance);
    BOOST_CHECK_CLOSE(volatility->blackVol(asof + 50 * Years, 1000.0), configuredVolatility, testTolerance);
    BOOST_CHECK_CLOSE(volatility->blackVol(asof + 50 * Years, 1200.0), configuredVolatility, testTolerance);
}

BOOST_AUTO_TEST_CASE(testCommodityVolCurveTypeCurve) {

    BOOST_TEST_MESSAGE("Testing commodity vol curve building with time dependent volatilities");

    // As of date
    Date asof(5, Feb, 2016);

    // Volatility configuration with time dependent volatilities
    bool extrapolate = true;
    vector<string> quotes{"COMMODITY_OPTION/RATE_LNVOL/GOLD/USD/1Y/ATMF",
                          "COMMODITY_OPTION/RATE_LNVOL/GOLD/USD/2Y/ATMF",
                          "COMMODITY_OPTION/RATE_LNVOL/GOLD/USD/5Y/ATMF"};
    boost::shared_ptr<CommodityVolatilityCurveConfig> curveConfig = boost::make_shared<CommodityVolatilityCurveConfig>(
        "GOLD_USD_VOLS", "", "USD", quotes, "A365", "NullCalendar", extrapolate);

    // Curve configurations
    CurveConfigurations curveConfigs;
    curveConfigs.commodityVolatilityCurveConfig("GOLD_USD_VOLS") = curveConfig;

    // Commodity curve spec
    CommodityVolatilityCurveSpec curveSpec("USD", "GOLD_USD_VOLS");

    // Market data loader
    MockLoader loader;

    // Check commodity volatility construction works
    boost::shared_ptr<CommodityVolCurve> curve;
    BOOST_CHECK_NO_THROW(curve = boost::make_shared<CommodityVolCurve>(asof, curveSpec, loader, curveConfigs));

    // Check time depending volatilities are as expected
    boost::shared_ptr<BlackVolTermStructure> volatility = curve->volatility();
    Real configuredVolatility;

    // Check configured pillar points: { (1Y, 0.11), (2Y, 0.10), (5Y, 0.09) }
    // Check also strike independence
    configuredVolatility = 0.11;
    BOOST_CHECK_CLOSE(volatility->blackVol(asof + 1 * Years, 1000.0), configuredVolatility, testTolerance);
    BOOST_CHECK_CLOSE(volatility->blackVol(asof + 1 * Years, 1200.0), configuredVolatility, testTolerance);

    configuredVolatility = 0.10;
    BOOST_CHECK_CLOSE(volatility->blackVol(asof + 2 * Years, 1000.0), configuredVolatility, testTolerance);
    BOOST_CHECK_CLOSE(volatility->blackVol(asof + 2 * Years, 1200.0), configuredVolatility, testTolerance);

    configuredVolatility = 0.09;
    BOOST_CHECK_CLOSE(volatility->blackVol(asof + 5 * Years, 1000.0), configuredVolatility, testTolerance);
    BOOST_CHECK_CLOSE(volatility->blackVol(asof + 5 * Years, 1200.0), configuredVolatility, testTolerance);

    // Check briefly the default linear interpolation and extrapolation
    Time t_s = volatility->dayCounter().yearFraction(asof, asof + 2 * Years);
    Real v_s = 0.10;
    Time t_e = volatility->dayCounter().yearFraction(asof, asof + 5 * Years);
    Real v_e = 0.09;
    // at 3 years
    Time t = volatility->dayCounter().yearFraction(asof, asof + 3 * Years);
    Real v = sqrt((v_s * v_s * t_s + (v_e * v_e * t_e - v_s * v_s * t_s) * (t - t_s) / (t_e - t_s)) / t);
    BOOST_CHECK_CLOSE(volatility->blackVol(asof + 3 * Years, 1000.0), v, testTolerance);
    BOOST_CHECK_CLOSE(volatility->blackVol(asof + 3 * Years, 1200.0), v, testTolerance);
    // at 6 years, extrapolation is with a flat vol
    BOOST_CHECK_CLOSE(volatility->blackVol(asof + 6 * Years, 1000.0), v_e, testTolerance);
    BOOST_CHECK_CLOSE(volatility->blackVol(asof + 6 * Years, 1200.0), v_e, testTolerance);
}

BOOST_AUTO_TEST_CASE(testCommodityVolCurveTypeSurface) {

    BOOST_TEST_MESSAGE("Testing commodity vol curve building with time and strike dependent volatilities");

    // As of date
    Date asof(5, Feb, 2016);

    // Volatility configuration with time dependent volatilities
    bool extrapolate = true;
    vector<string> expiries{"1Y", "2Y", "5Y"};
    vector<string> strikes{"1150", "1190"};
    boost::shared_ptr<CommodityVolatilityCurveConfig> curveConfig = boost::make_shared<CommodityVolatilityCurveConfig>(
        "GOLD_USD_VOLS", "", "USD", expiries, strikes, "A365", "NullCalendar", extrapolate);

    // Curve configurations
    CurveConfigurations curveConfigs;
    curveConfigs.commodityVolatilityCurveConfig("GOLD_USD_VOLS") = curveConfig;

    // Commodity curve spec
    CommodityVolatilityCurveSpec curveSpec("USD", "GOLD_USD_VOLS");

    // Market data loader
    MockLoader loader;

    // Check commodity volatility construction works
    boost::shared_ptr<CommodityVolCurve> curve;
    BOOST_CHECK_NO_THROW(curve = boost::make_shared<CommodityVolCurve>(asof, curveSpec, loader, curveConfigs));

    // Check time and strike depending volatilities are as expected
    boost::shared_ptr<BlackVolTermStructure> volatility = curve->volatility();

    // Check configured pillar points
    BOOST_CHECK_CLOSE(volatility->blackVol(asof + 1 * Years, 1150.0), 0.105, testTolerance);
    BOOST_CHECK_CLOSE(volatility->blackVol(asof + 1 * Years, 1190.0), 0.115, testTolerance);
    BOOST_CHECK_CLOSE(volatility->blackVol(asof + 2 * Years, 1150.0), 0.095, testTolerance);
    BOOST_CHECK_CLOSE(volatility->blackVol(asof + 2 * Years, 1190.0), 0.105, testTolerance);
    BOOST_CHECK_CLOSE(volatility->blackVol(asof + 5 * Years, 1150.0), 0.085, testTolerance);
    BOOST_CHECK_CLOSE(volatility->blackVol(asof + 5 * Years, 1190.0), 0.095, testTolerance);
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()