/**************************************************************************
 * Copyright (c) 2017-2019 by the mfmg authors                            *
 * All rights reserved.                                                   *
 *                                                                        *
 * This file is part of the mfmg library. mfmg is distributed under a BSD *
 * 3-clause license. For the licensing terms see the LICENSE file in the  *
 * top-level directory                                                    *
 *                                                                        *
 * SPDX-License-Identifier: BSD-3-Clause                                  *
 *************************************************************************/

#ifndef MFMG_TEST_HIERARCHY_HELPERS_HPP
#define MFMG_TEST_HIERARCHY_HELPERS_HPP

#include <mfmg/common/hierarchy.hpp>
#include <mfmg/dealii/dealii_matrix_free_mesh_evaluator.hpp>
#include <mfmg/dealii/dealii_mesh_evaluator.hpp>

#include <deal.II/base/conditional_ostream.h>
#include <deal.II/dofs/dof_accessor.h>
#include <deal.II/fe/fe_q.h>
#include <deal.II/lac/la_parallel_vector.h>
#include <deal.II/lac/precondition.h>
#include <deal.II/lac/solver_cg.h>
#include <deal.II/lac/trilinos_linear_operator.h>
#include <deal.II/lac/trilinos_vector.h>

#include <boost/property_tree/info_parser.hpp>
#include <boost/test/data/monomorphic.hpp>
#include <boost/test/data/test_case.hpp>

#include <random>

#include "laplace.hpp"
#include "laplace_matrix_free.hpp"

// If activated the global operator's ShapeInfo object is copied instead of
// recalculated when initializing the agglomerate operators. Requires that
// deal.II is patched with scripts/0002-Avoid-copying-shape-info.patch.
//
// #define USE_OPTIMIZED_AGGLOMERATE_INITIALIZATION

namespace bdata = boost::unit_test::data;
namespace tt = boost::test_tools;

template <int dim>
class Source final : public dealii::Function<dim>
{
public:
  Source() = default;

  virtual ~Source() override = default;

  virtual double value(dealii::Point<dim> const &,
                       unsigned int const = 0) const override
  {
    return 0.;
  }
};

template <int dim>
class Coefficient : public dealii::Function<dim>
{
public:
  virtual ~Coefficient() override = default;

  virtual dealii::VectorizedArray<double>
  value(dealii::Point<dim, dealii::VectorizedArray<double>> const &p,
        unsigned int const = 0) const = 0;

  virtual double value(dealii::Point<dim> const &p,
                       unsigned int const component = 0) const override = 0;
};

template <int dim>
class ConstantMaterialProperty final : public Coefficient<dim>
{
public:
  ConstantMaterialProperty() = default;

  virtual ~ConstantMaterialProperty() override = default;

  virtual dealii::VectorizedArray<double>
  value(dealii::Point<dim, dealii::VectorizedArray<double>> const &,
        unsigned int const = 0) const override
  {
    return dealii::make_vectorized_array<double>(1.);
  }

  virtual double value(dealii::Point<dim> const &,
                       unsigned int const = 0) const override
  {
    return 1.;
  }
};

template <int dim>
class LinearXMaterialProperty final : public Coefficient<dim>
{
public:
  LinearXMaterialProperty() = default;

  virtual ~LinearXMaterialProperty() override = default;

  virtual dealii::VectorizedArray<double>
  value(dealii::Point<dim, dealii::VectorizedArray<double>> const &p,
        unsigned int const = 0) const override
  {
    auto const one = dealii::make_vectorized_array<double>(1.);
    return one + std::abs(p[0]);
  }

  virtual double value(dealii::Point<dim> const &p,
                       unsigned int const = 0) const override
  {
    return 1. + std::abs(p[0]);
  }
};

template <int dim>
class LinearMaterialProperty final : public Coefficient<dim>
{
public:
  LinearMaterialProperty() = default;

  virtual ~LinearMaterialProperty() override = default;

  virtual dealii::VectorizedArray<double>
  value(dealii::Point<dim, dealii::VectorizedArray<double>> const &p,
        unsigned int const = 0) const override
  {
    auto const one = dealii::make_vectorized_array<double>(1.);
    auto val = one;
    for (unsigned int d = 0; d < dim; ++d)
      val += (one + static_cast<double>(d) * one) * std::abs(p[d]);

    return val;
  }

  virtual double value(dealii::Point<dim> const &p,
                       unsigned int const = 0) const override
  {
    double val = 1.;
    for (unsigned int d = 0; d < dim; ++d)
      val += (1. + d) * std::abs(p[d]);

    return val;
  }
};

template <int dim>
class DiscontinuousMaterialProperty final : public Coefficient<dim>
{
public:
  DiscontinuousMaterialProperty() = default;

  virtual ~DiscontinuousMaterialProperty() override = default;

  virtual dealii::VectorizedArray<double>
  value(dealii::Point<dim, dealii::VectorizedArray<double>> const &p,
        unsigned int const = 0) const override
  {
    auto const one = dealii::make_vectorized_array<double>(1.);
    unsigned int dim_scale = 0;
    for (unsigned int d = 0; d < dim; ++d)
      dim_scale += static_cast<unsigned int>(std::floor(p[d][0] * 100)) % 2;

    return (dim_scale == dim ? 100. * one : 10. * one);
  }

  virtual double value(dealii::Point<dim> const &p,
                       unsigned int const = 0) const override

  {
    unsigned int dim_scale = 0;
    for (unsigned int d = 0; d < dim; ++d)
      dim_scale += static_cast<unsigned int>(std::floor(p[d] * 100)) % 2;

    return (dim_scale == dim ? 100. : 10.);
  }
};

template <int dim>
class MaterialPropertyFactory
{
public:
  static std::shared_ptr<Coefficient<dim>>
  create_material_property(std::string const &material_type)
  {
    if (material_type == "constant")
      return std::make_shared<ConstantMaterialProperty<dim>>();
    else if (material_type == "linear_x")
      return std::make_shared<LinearXMaterialProperty<dim>>();
    else if (material_type == "linear")
      return std::make_shared<LinearMaterialProperty<dim>>();
    else if (material_type == "discontinuous")
      return std::make_shared<DiscontinuousMaterialProperty<dim>>();
    else
    {
      mfmg::ASSERT_THROW_NOT_IMPLEMENTED();

      return nullptr;
    }
  }
};

template <typename MeshEvaluator>
class TestMeshEvaluator final : public MeshEvaluator
{
public:
  static int constexpr dim = MeshEvaluator::_dim;
  TestMeshEvaluator(dealii::DoFHandler<dim> &dof_handler,
                    dealii::AffineConstraints<double> &constraints,
                    unsigned int fe_degree,
                    dealii::TrilinosWrappers::SparseMatrix const &matrix,
                    std::shared_ptr<dealii::Function<dim>> material_property)
      : MeshEvaluator(dof_handler, constraints), _fe_degree(fe_degree),
        _matrix(matrix), _material_property(material_property)
  {
  }

  virtual ~TestMeshEvaluator() override = default;

  virtual void evaluate_global(
      dealii::DoFHandler<dim> &, dealii::AffineConstraints<double> &,
      dealii::TrilinosWrappers::SparseMatrix &system_matrix) const override
  {
    // TODO this is pretty expansive, we should use a shared pointer
    system_matrix.copy_from(_matrix);
  }

  virtual void evaluate_agglomerate(
      dealii::DoFHandler<dim> &dof_handler,
      dealii::AffineConstraints<double> &constraints,
      dealii::SparsityPattern &system_sparsity_pattern,
      dealii::SparseMatrix<double> &system_matrix) const override
  {
    unsigned int const fe_degree = _fe_degree;
    dealii::FE_Q<dim> fe(fe_degree);
    dof_handler.distribute_dofs(fe);

    dealii::IndexSet locally_relevant_dofs;
    dealii::DoFTools::extract_locally_relevant_dofs(dof_handler,
                                                    locally_relevant_dofs);

    // Compute the constraints
    constraints.clear();
    constraints.reinit(locally_relevant_dofs);
    dealii::DoFTools::make_hanging_node_constraints(dof_handler, constraints);
    dealii::VectorTools::interpolate_boundary_values(
        dof_handler, 1, dealii::Functions::ZeroFunction<dim>(), constraints);
    constraints.close();

    // Build the system sparsity pattern and reinitialize the system sparse
    // matrix
    dealii::DynamicSparsityPattern dsp(dof_handler.n_dofs());
    dealii::DoFTools::make_sparsity_pattern(dof_handler, dsp, constraints);
    system_sparsity_pattern.copy_from(dsp);
    system_matrix.reinit(system_sparsity_pattern);

    // Fill the system matrix
    dealii::QGauss<dim> const quadrature(fe_degree + 1);
    dealii::FEValues<dim> fe_values(
        fe, quadrature,
        dealii::update_values | dealii::update_gradients |
            dealii::update_quadrature_points | dealii::update_JxW_values);
    unsigned int const dofs_per_cell = fe.dofs_per_cell;
    unsigned int const n_q_points = quadrature.size();
    dealii::FullMatrix<double> cell_matrix(dofs_per_cell, dofs_per_cell);
    std::vector<dealii::types::global_dof_index> local_dof_indices(
        dofs_per_cell);
    for (auto cell :
         dealii::filter_iterators(dof_handler.active_cell_iterators(),
                                  dealii::IteratorFilters::LocallyOwnedCell()))
    {
      cell_matrix = 0;
      fe_values.reinit(cell);

      for (unsigned int q_point = 0; q_point < n_q_points; ++q_point)
      {
        double const diffusion_coefficient =
            _material_property->value(fe_values.quadrature_point(q_point));
        for (unsigned int i = 0; i < dofs_per_cell; ++i)
          for (unsigned int j = 0; j < dofs_per_cell; ++j)
            cell_matrix(i, j) +=
                diffusion_coefficient * fe_values.shape_grad(i, q_point) *
                fe_values.shape_grad(j, q_point) * fe_values.JxW(q_point);
      }

      cell->get_dof_indices(local_dof_indices);
      constraints.distribute_local_to_global(cell_matrix, local_dof_indices,
                                             system_matrix);
    }
  }

private:
  unsigned const _fe_degree;
  dealii::TrilinosWrappers::SparseMatrix const &_matrix;
  std::shared_ptr<dealii::Function<dim>> _material_property;
};

template <int dim, int fe_degree, typename ScalarType>
class TestMFMeshEvaluator final
    : public mfmg::DealIIMatrixFreeMeshEvaluator<dim>
{
public:
  TestMFMeshEvaluator(
      dealii::DoFHandler<dim> &dof_handler,
      dealii::AffineConstraints<double> &constraints,
      LaplaceOperator<dim, fe_degree, ScalarType> &laplace_operator,
      std::shared_ptr<Coefficient<dim>> material_property)
      : mfmg::DealIIMatrixFreeMeshEvaluator<dim>(dof_handler, constraints),
        _material_property(material_property), _fe(fe_degree),
        _laplace_operator(laplace_operator)
  {
  }

  // We need a copy constructor since the evaluator has to cloned for each
  // agglomerate. Here, we just copy the minimum number of member variables
  // required. In particular, we should not need to copy mutable members since
  // they are set up in matrix_free_initialize_agglomerate only.
  TestMFMeshEvaluator(
      TestMFMeshEvaluator<dim, fe_degree, ScalarType> const &_other_evaluator)
      : mfmg::DealIIMatrixFreeMeshEvaluator<dim>(*this),
        _material_property(_other_evaluator._material_property),
        _fe(_other_evaluator._fe),
        _laplace_operator(_other_evaluator._laplace_operator)
  {
  }

  virtual ~TestMFMeshEvaluator() override = default;

  virtual std::unique_ptr<mfmg::DealIIMatrixFreeMeshEvaluator<dim>>
  clone() const override
  {
    return std::make_unique<TestMFMeshEvaluator>(*this);
  }

  virtual void
  matrix_free_evaluate_agglomerate(dealii::Vector<double> const &src,
                                   dealii::Vector<double> &dst) const override
  {
    // Unfortunately, dealii::MatrixFreeOperators::Base only supports
    // dealii::LinearAlgebra::distributed::Vector so unless we duplicate a lot
    // of code, we need copy src and dst.
    std::copy(src.begin(), src.end(), distributed_src.begin());
    _agg_laplace_operator->vmult(distributed_dst, distributed_src);
    std::copy(distributed_dst.begin(), distributed_dst.end(), dst.begin());
  }

  virtual std::vector<double> matrix_free_get_agglomerate_diagonal(
      dealii::AffineConstraints<double> &constraints) const override
  {
    constraints.copy_from(_agg_constraints);

    auto diag_matrix = _agg_laplace_operator->get_matrix_diagonal();
    auto diag_dealii_vector = diag_matrix->get_vector();

    return std::vector<double>(diag_dealii_vector.begin(),
                               diag_dealii_vector.end());
  }

  virtual void matrix_free_evaluate_global(
      dealii::LinearAlgebra::distributed::Vector<double> const &src,
      dealii::LinearAlgebra::distributed::Vector<double> &dst) const override
  {
    _laplace_operator.vmult(dst, src);
  }

  virtual std::shared_ptr<dealii::DiagonalMatrix<
      dealii::LinearAlgebra::distributed::Vector<double>>>
  matrix_free_get_diagonal_inverse() const override
  {
    return _laplace_operator.get_matrix_diagonal_inverse();
  }

  virtual dealii::LinearAlgebra::distributed::Vector<double>
  get_diagonal() override
  {
    auto vector = _laplace_operator.get_matrix_diagonal()->get_vector();
    vector.update_ghost_values();

    return vector;
  }

  virtual void matrix_free_initialize_agglomerate(
      dealii::DoFHandler<dim> &dof_handler) const override
  {
    // FIXME dof_handler should be const and initialized somewhere else
    dof_handler.distribute_dofs(_fe);

    dealii::IndexSet locally_relevant_dofs;
    dealii::DoFTools::extract_locally_relevant_dofs(dof_handler,
                                                    locally_relevant_dofs);
    // Compute the constraints
    _agg_constraints.clear();
    _agg_constraints.reinit(locally_relevant_dofs);
    dealii::DoFTools::make_hanging_node_constraints(dof_handler,
                                                    _agg_constraints);
    dealii::VectorTools::interpolate_boundary_values(
        dof_handler, 1, dealii::Functions::ZeroFunction<dim>(),
        _agg_constraints);
    _agg_constraints.close();

    // Initialize the MatrixFree object
    typename dealii::MatrixFree<dim, ScalarType>::AdditionalData
        additional_data;
    additional_data.tasks_parallel_scheme =
        dealii::MatrixFree<dim, ScalarType>::AdditionalData::none;
    additional_data.mapping_update_flags = dealii::update_gradients |
                                           dealii::update_JxW_values |
                                           dealii::update_quadrature_points;
    std::shared_ptr<dealii::MatrixFree<dim, ScalarType>> mf_storage(
        new dealii::MatrixFree<dim, ScalarType>());
    mf_storage->reinit(
        dof_handler, _agg_constraints, dealii::QGauss<1>(fe_degree + 1),
        additional_data
#ifdef USE_OPTIMIZED_AGGLOMERATE_INITIALIZATION
        ,
        &(_laplace_operator.get_matrix_free()->get_raw_shape_info())
#endif
    );

    _agg_laplace_operator =
        std::make_unique<LaplaceOperator<dim, fe_degree, ScalarType>>();
    _agg_laplace_operator->initialize(mf_storage);
    _agg_laplace_operator->evaluate_coefficient(*_material_property);
    _agg_laplace_operator->compute_diagonal();

    distributed_dst.reinit(dof_handler.n_dofs());
    distributed_src.reinit(dof_handler.n_dofs());
  }

private:
  std::shared_ptr<Coefficient<dim>> _material_property;
  dealii::FE_Q<dim> _fe;
  mutable dealii::AffineConstraints<double> _agg_constraints;
  LaplaceOperator<dim, fe_degree, ScalarType> &_laplace_operator;
  mutable std::unique_ptr<LaplaceOperator<dim, fe_degree, ScalarType>>
      _agg_laplace_operator;
  mutable dealii::LinearAlgebra::distributed::Vector<ScalarType>
      distributed_dst;
  mutable dealii::LinearAlgebra::distributed::Vector<ScalarType>
      distributed_src;
};

#endif // #ifdef MFMG_TEST_HIERARCHY_HELPERS_HPP


// Copyright 2011-2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "third_party/zynamics/binexport/reader/graph_utility.h"

#include <utility>
#include <vector>

#include <boost/graph/adjacency_list.hpp>  // NOLINT

#include "gmock/gmock.h"
#include "gtest/gtest.h"

namespace security::binexport {

class GraphUtilityTest : public testing::Test {
 protected:
  const std::vector<Address> addresses_{0,  1,  2,  3,  4,  5,  6,  7,  8,  9,
                                        10, 11, 12, 13, 14, 15, 16, 16, 18, 19};

  using edge = std::pair<uint32_t, uint32_t>;
  const std::vector<edge> edges_{
      {0, 1},   {1, 2},   {2, 3},   {3, 4},   {4, 5},   {5, 6},   {5, 7},
      {5, 8},   {5, 9},   {6, 10},  {7, 10},  {8, 10},  {9, 10},  {10, 11},
      {11, 12}, {12, 13}, {13, 14}, {14, 15}, {14, 15}, {15, 16}, {16, 17},
      {17, 18}, {18, 19}, {1, 18},  {17, 2}};

  //    [0]->[1]->[2]->[3]->[4]->[5]->[6]->[10]->[11]->[12]->[13]->[14]->[15]
  //          ^    ^              +-->[7]----^                       +----^|
  //          |    |              +-->[8]----^                             |
  //          |    |              +-->[9]----^                             |
  //          |    +---------------------------------------------+         |
  //          +-------------------------------------------+      |         |
  //                                               [19]<-[18]<-[17]<-[16]<-+
  // [20] <- alone to test if the graph hashing works if a vertex is not
  // connected by any edge.

  struct VertexInfo {
    Address address;
  };

 public:
  struct EdgeProperty {};

  using Graph =
      boost::compressed_sparse_row_graph<boost::bidirectionalS, VertexInfo,
                                         EdgeProperty, boost::no_property,
                                         uint32_t, uint32_t>;

  Graph graph_;

  using Vertex = boost::graph_traits<Graph>::vertex_descriptor;
  using Edge = boost::graph_traits<Graph>::edge_descriptor;
  using EdgeIterator = boost::graph_traits<Graph>::edge_iterator;
  using OutEdgeIterator = boost::graph_traits<Graph>::out_edge_iterator;
  using InEdgeIterator = boost::graph_traits<Graph>::in_edge_iterator;
  using AdjacencyIterator = boost::graph_traits<Graph>::adjacency_iterator;

  using VertexIndexMap =
      boost::property_map<Graph, boost::vertex_index_t>::type;

  using UndirectedGraph =
      boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS,
                            VertexInfo, EdgeProperty>;

 protected:
  void SetUp() override {
    graph_ = Graph(boost::edges_are_unsorted_multi_pass, edges_.begin(),
                   edges_.end(), addresses_.size());
    using VertexIterator = boost::graph_traits<Graph>::vertex_iterator;
    VertexIterator vertices_it, vertices_end;
    int j = 0;
    for (boost::tie(vertices_it, vertices_end) = boost::vertices(graph_);
         vertices_it != vertices_end; ++vertices_it, ++j) {
      graph_[j].address = addresses_.at(j);
    }
  }
};

TEST_F(GraphUtilityTest, GetEdgeVector) {
  const std::pair<Edge, bool> edge_pair(
      boost::edge(Vertex(5), Vertex(6), graph_));
  const EdgeDegrees edge_vector =
      GetEdgeDegrees<GraphUtilityTest>(graph_, edge_pair.first);
  EXPECT_EQ(edge_vector.source_in_degree, 1);
  EXPECT_EQ(edge_vector.source_out_degree, 4);
  EXPECT_EQ(edge_vector.target_in_degree, 1);
  EXPECT_EQ(edge_vector.target_out_degree, 1);
}

}  // namespace security::binexport


//==================================================================================================
/*!
  @file
  @copyright 2016 NumScale SAS

  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_DEINTERLEAVE_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_DEINTERLEAVE_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{
  /*!
    @ingroup group-swar
    This function object performs a complete deinterleaving of its arguments.

    deinterleave is guaranteed to be optimized as much as possible compared to using
    boost::simd::deinterleave_first and boost::simd::deinterleave_second in succession.

    @par Semantic:
    For every parameters @c x and @c y of type @c T :
    @code
    std::array<T,2> r = deinterleave(x,y);
    @endcode
    is equivalent to :
    @code
    std::array<T,2> r = { deinterleave_first(x,y), deinterleave_second(x,y) };
    @endcode

    @param  v0 First argument to deinterleave
    @param  v1 Second argument to deinterleave
    @return An array of two boost::simd::pack containing the deinterleaved data from the arguments
  **/
  std::array<Value,2> deinterleave(Value const& v0, Value const& v1);
} }
#endif

#include <boost/simd/function/simd/deinterleave.hpp>

#endif


#include <boost/test/unit_test.hpp>
#include <scorum/protocol/betting/market.hpp>
#include <defines.hpp>

namespace {
using namespace scorum;
using namespace scorum::protocol;

BOOST_AUTO_TEST_SUITE(market_type_tests)

SCORUM_TEST_CASE(create_wincases_test)
{
    {
        market_type m = result_home{};
        auto wincases = create_wincases(m);
        BOOST_CHECK_NO_THROW(wincases.first.get<result_home::yes>());
        BOOST_CHECK_NO_THROW(wincases.second.get<result_home::no>());
    }
    {
        auto wincases = create_wincases(result_draw{});
        BOOST_CHECK_NO_THROW(wincases.first.get<result_draw::yes>());
        BOOST_CHECK_NO_THROW(wincases.second.get<result_draw::no>());
    }
    {
        auto m = handicap{ -500 };
        auto wincases = create_wincases(m);
        BOOST_CHECK_NO_THROW(wincases.first.get<handicap::over>());
        BOOST_CHECK_NO_THROW(wincases.second.get<handicap::under>());
        BOOST_CHECK_EQUAL(wincases.first.get<handicap::over>().threshold, -500);
        BOOST_CHECK_EQUAL(wincases.second.get<handicap::under>().threshold, -500);
    }
    {
        auto m = correct_score{ 1, 2 };
        auto wincases = create_wincases(m);
        BOOST_CHECK_NO_THROW(wincases.first.get<correct_score::yes>());
        BOOST_CHECK_NO_THROW(wincases.second.get<correct_score::no>());
        BOOST_CHECK_EQUAL(wincases.first.get<correct_score::yes>().home, 1);
        BOOST_CHECK_EQUAL(wincases.first.get<correct_score::yes>().away, 2);
        BOOST_CHECK_EQUAL(wincases.second.get<correct_score::no>().home, 1);
        BOOST_CHECK_EQUAL(wincases.second.get<correct_score::no>().away, 2);
    }
}

SCORUM_TEST_CASE(has_trd_state_test_positive)
{
    BOOST_CHECK(has_trd_state(handicap{ 0 }));
    BOOST_CHECK(has_trd_state(handicap{ 1000 }));
    BOOST_CHECK(has_trd_state(handicap{ 2000 }));
    BOOST_CHECK(has_trd_state(handicap{ -2000 }));

    BOOST_CHECK(has_trd_state(total{ 1000 }));
    BOOST_CHECK(has_trd_state(total{ 2000 }));
}

SCORUM_TEST_CASE(has_trd_state_test_negative)
{
    BOOST_CHECK(!has_trd_state(handicap{ 500 }));
    BOOST_CHECK(!has_trd_state(handicap{ 1500 }));
    BOOST_CHECK(!has_trd_state(handicap{ 2500 }));
    BOOST_CHECK(!has_trd_state(handicap{ -1500 }));
    BOOST_CHECK(!has_trd_state(total{ 500 }));
    BOOST_CHECK(!has_trd_state(total{ 1500 }));
    BOOST_CHECK(!has_trd_state(result_home{}));
    BOOST_CHECK(!has_trd_state(result_draw{}));
    BOOST_CHECK(!has_trd_state(correct_score_away{}));
    BOOST_CHECK(!has_trd_state(correct_score{ 3, 2 }));
    BOOST_CHECK(!has_trd_state(goal_away{}));
}

BOOST_AUTO_TEST_SUITE_END()
}


// Copyright (c) 2019 The Bitcoin SV developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "util.h"
#include "validation.h"
#include "test/test_bitcoin.h"

#include <boost/test/unit_test.hpp>

/**
 * Testing txn validation utils.
 *
 * 1. A number of Low and High priority threads (default estimations):
 * - GetNumLowPriorityValidationThrs()
 * - GetNumHighPriorityValidationThrs()
 */

BOOST_FIXTURE_TEST_SUITE(test_txnvalidation_utils, BasicTestingSetup)

/**
 * A basic static test case.
 *
 * Checks based on the given set [0, 24] of Hardware Concurrency Threads.
 */
BOOST_AUTO_TEST_CASE(test_number_of_priority_threads_static) {
    // Hardware Concurrency Threads: 0
    BOOST_CHECK_EQUAL(1, GetNumLowPriorityValidationThrs(0));
    BOOST_CHECK_EQUAL(1, GetNumHighPriorityValidationThrs(0));
    // Hardware Concurrency Threads: 1
    BOOST_CHECK_EQUAL(1, GetNumLowPriorityValidationThrs(1));
    BOOST_CHECK_EQUAL(1, GetNumHighPriorityValidationThrs(1));
    // Hardware Concurrency Threads: 2
    BOOST_CHECK_EQUAL(1, GetNumLowPriorityValidationThrs(2));
    BOOST_CHECK_EQUAL(1, GetNumHighPriorityValidationThrs(2));
    // Hardware Concurrency Threads: 3
    BOOST_CHECK_EQUAL(1, GetNumLowPriorityValidationThrs(3));
    BOOST_CHECK_EQUAL(2, GetNumHighPriorityValidationThrs(3));
    // Hardware Concurrency Threads: 4
    BOOST_CHECK_EQUAL(1, GetNumLowPriorityValidationThrs(4));
    BOOST_CHECK_EQUAL(3, GetNumHighPriorityValidationThrs(4));
    // Hardware Concurrency Threads: 5
    BOOST_CHECK_EQUAL(1, GetNumLowPriorityValidationThrs(5));
    BOOST_CHECK_EQUAL(4, GetNumHighPriorityValidationThrs(5));
    // Hardware Concurrency Threads: 6
    BOOST_CHECK_EQUAL(1, GetNumLowPriorityValidationThrs(6));
    BOOST_CHECK_EQUAL(5, GetNumHighPriorityValidationThrs(6));
    // Hardware Concurrency Threads: 7
    BOOST_CHECK_EQUAL(1, GetNumLowPriorityValidationThrs(7));
    BOOST_CHECK_EQUAL(6, GetNumHighPriorityValidationThrs(7));
    // Hardware Concurrency Threads: 8
    BOOST_CHECK_EQUAL(2, GetNumLowPriorityValidationThrs(8));
    BOOST_CHECK_EQUAL(6, GetNumHighPriorityValidationThrs(8));
    // Hardware Concurrency Threads: 9
    BOOST_CHECK_EQUAL(2, GetNumLowPriorityValidationThrs(9));
    BOOST_CHECK_EQUAL(7, GetNumHighPriorityValidationThrs(9));
    // Hardware Concurrency Threads: 10
    BOOST_CHECK_EQUAL(2, GetNumLowPriorityValidationThrs(10));
    BOOST_CHECK_EQUAL(8, GetNumHighPriorityValidationThrs(10));
    // Hardware Concurrency Threads: 11
    BOOST_CHECK_EQUAL(2, GetNumLowPriorityValidationThrs(11));
    BOOST_CHECK_EQUAL(9, GetNumHighPriorityValidationThrs(11));
    // Hardware Concurrency Threads: 12
    BOOST_CHECK_EQUAL(3, GetNumLowPriorityValidationThrs(12));
    BOOST_CHECK_EQUAL(9, GetNumHighPriorityValidationThrs(12));
    // Hardware Concurrency Threads: 13
    BOOST_CHECK_EQUAL(3, GetNumLowPriorityValidationThrs(13));
    BOOST_CHECK_EQUAL(10, GetNumHighPriorityValidationThrs(13));
    // Hardware Concurrency Threads: 14
    BOOST_CHECK_EQUAL(3, GetNumLowPriorityValidationThrs(14));
    BOOST_CHECK_EQUAL(11, GetNumHighPriorityValidationThrs(14));
    // Hardware Concurrency Threads: 15
    BOOST_CHECK_EQUAL(3, GetNumLowPriorityValidationThrs(15));
    BOOST_CHECK_EQUAL(12, GetNumHighPriorityValidationThrs(15));
    // Hardware Concurrency Threads: 16
    BOOST_CHECK_EQUAL(4, GetNumLowPriorityValidationThrs(16));
    BOOST_CHECK_EQUAL(12, GetNumHighPriorityValidationThrs(16));
    // Hardware Concurrency Threads: 17
    BOOST_CHECK_EQUAL(4, GetNumLowPriorityValidationThrs(17));
    BOOST_CHECK_EQUAL(13, GetNumHighPriorityValidationThrs(17));
    // Hardware Concurrency Threads: 18
    BOOST_CHECK_EQUAL(4, GetNumLowPriorityValidationThrs(18));
    BOOST_CHECK_EQUAL(14, GetNumHighPriorityValidationThrs(18));
    // Hardware Concurrency Threads: 19
    BOOST_CHECK_EQUAL(4, GetNumLowPriorityValidationThrs(19));
    BOOST_CHECK_EQUAL(15, GetNumHighPriorityValidationThrs(19));
    // Hardware Concurrency Threads: 20
    BOOST_CHECK_EQUAL(5, GetNumLowPriorityValidationThrs(20));
    BOOST_CHECK_EQUAL(15, GetNumHighPriorityValidationThrs(20));
    // Hardware Concurrency Threads: 21
    BOOST_CHECK_EQUAL(5, GetNumLowPriorityValidationThrs(21));
    BOOST_CHECK_EQUAL(16, GetNumHighPriorityValidationThrs(21));
    // Hardware Concurrency Threads: 22
    BOOST_CHECK_EQUAL(5, GetNumLowPriorityValidationThrs(22));
    BOOST_CHECK_EQUAL(17, GetNumHighPriorityValidationThrs(22));
    // Hardware Concurrency Threads: 23
    BOOST_CHECK_EQUAL(5, GetNumLowPriorityValidationThrs(23));
    BOOST_CHECK_EQUAL(18, GetNumHighPriorityValidationThrs(23));
    // Hardware Concurrency Threads: 24
    BOOST_CHECK_EQUAL(6, GetNumLowPriorityValidationThrs(24));
    BOOST_CHECK_EQUAL(18, GetNumHighPriorityValidationThrs(24));
}

/**
 * A dynamic test case.
 *
 * Checks based on the given set [25, 100K] of Hardware Concurrency Threads.
 */
BOOST_AUTO_TEST_CASE(test_number_of_priority_threads_dynamic) {
    for (size_t n=25; n < 100000; ++n) {
        // A number of Low priority threads
        size_t nEstimateNumLowPriorityThrs = static_cast<size_t>(n * 0.25);
        size_t nLowPriorityThrs = GetNumLowPriorityValidationThrs(n);
        BOOST_CHECK(nLowPriorityThrs > 0);
        BOOST_CHECK(nLowPriorityThrs >= nEstimateNumLowPriorityThrs);
        // A number of High priority threads
        size_t nEstimateNumHighPriorityThrs = n - nEstimateNumLowPriorityThrs;
        size_t nHighPriorityThrs = GetNumHighPriorityValidationThrs(n);
        BOOST_CHECK(nHighPriorityThrs >= nEstimateNumHighPriorityThrs);
        BOOST_CHECK(nHighPriorityThrs > 0);
        BOOST_CHECK(nHighPriorityThrs < n);
        // A number of high priority threads should be greater than a number of low priority threads.
        BOOST_CHECK(nHighPriorityThrs > nLowPriorityThrs);
    }
}

BOOST_AUTO_TEST_SUITE_END()


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

#include <Eigen/Eigenvalues>
#include <Eigen/SparseCore>
//#include <Spectra/GenEigsSolver.h>
//#include <Spectra/MatOp/SparseGenMatProd.h>
//#include <Spectra/SymEigsSolver.h>
//#include <Spectra/MatOp/SparseSymMatProd.h>

#include "mps_voxels/video_graph.h"
#include "mps_voxels/graph_matrix_utils.h"
#include <mps_msgs/SegmentGraph.h>

#include <boost/graph/graph_traits.hpp>
#include <boost/graph/connected_components.hpp>
#include <boost/graph/graphviz.hpp>

#include <ros/ros.h>

template <class WeightMap>
class edge_writer {
public:
	edge_writer(WeightMap w) : wm(w) {}
	template <class Edge>
	void operator()(std::ostream &out, const Edge& e) const {
		out << "[penwidth=\"" << wm[e]*4.0 << "\"]";
	}
private:
	WeightMap wm;
};

template <class WeightMap>
inline edge_writer<WeightMap>
make_edge_writer(WeightMap w) {
	return edge_writer<WeightMap>(w);
}

std::string node_name(const NodeProperties& np) {return "f" + std::to_string(np.t.toSec()) + "c" + std::to_string(np.leafID);}

void print(std::ostream& out, const VideoSegmentationGraph& G)
{
	out << "graph G {\n";

	std::map<ros::Time, std::vector<VideoSegmentationGraph::vertex_descriptor>> frames;
	for (VideoSegmentationGraph::vertex_descriptor vd : make_range(boost::vertices(G)))
	{
		frames[G[vd].t].push_back(vd);
	}

	for (const auto& frame : frames)
	{
		out << "subgraph cluster_" << frame.first << " {\n";
		out << "label=\"Frame " << frame.first << "\";\n";
		for (const VideoSegmentationGraph::vertex_descriptor vd : frame.second)
		{
			const NodeProperties& np = G[vd];
			out << node_name(np) << " [label=\"" << np.leafID << "\"];\n";
		}
		out << "}\n";
	}

	for (VideoSegmentationGraph::edge_descriptor ed : make_range(boost::edges(G)))
	{
		VideoSegmentationGraph::vertex_descriptor u = boost::source(ed, G);
		VideoSegmentationGraph::vertex_descriptor v = boost::target(ed, G);

		const EdgeProperties& ep = G[ed];
		const NodeProperties& npu = G[u];
		const NodeProperties& npv = G[v];
		out << node_name(npu) << "--" << node_name(npv) << " [penwidth=\"" << 4.0*ep.affinity << "\"];\n";
	}

	out << "}\n";
}

int main(int argc, char* argv[])
{
	ros::init(argc, argv, "test_graph_segmentation");
	ros::NodeHandle nh, pnh("~");

	ros::ServiceClient segmentClient = nh.serviceClient<mps_msgs::SegmentGraph>("/segment_graph");
	if (!segmentClient.waitForExistence(ros::Duration(3)))
	{
		ROS_ERROR("Segmentation server not connected.");
		return -1;
	}

	VideoSegmentationGraph G;

	std::map<std::pair<ros::Time, int>, VideoSegmentationGraph::vertex_descriptor> segmentToNode;
	std::vector<NodeProperties> nps{NodeProperties({ros::Time(0), 0}), NodeProperties({ros::Time(0), 1}), NodeProperties({ros::Time(0), 2}), NodeProperties({ros::Time(1), 0}), NodeProperties({ros::Time(1), 1})};
	for (const auto& np : nps)
	{
		// Add leaf i to graph
		// NB: labels start from 1, as 0 is background, so tree[0] = labels[1]
		VideoSegmentationGraph::vertex_descriptor v = boost::add_vertex(np, G);
		segmentToNode.insert({{np.t, np.leafID}, v});
	}

	boost::add_edge(segmentToNode[{ros::Time(0), 0}], segmentToNode[{ros::Time(0), 1}], {1.0/1.0}, G);
	boost::add_edge(segmentToNode[{ros::Time(0), 0}], segmentToNode[{ros::Time(0), 2}], {1.0/10.0}, G);
	boost::add_edge(segmentToNode[{ros::Time(0), 1}], segmentToNode[{ros::Time(0), 2}], {1.0/10.0}, G);
	boost::add_edge(segmentToNode[{ros::Time(1), 0}], segmentToNode[{ros::Time(1), 1}], {1.0/15.0}, G);
	boost::add_edge(segmentToNode[{ros::Time(0), 2}], segmentToNode[{ros::Time(1), 1}], {1.0/2.0}, G);
	boost::add_edge(segmentToNode[{ros::Time(0), 0}], segmentToNode[{ros::Time(1), 0}], {1.0/2.0}, G);
	boost::add_edge(segmentToNode[{ros::Time(0), 1}], segmentToNode[{ros::Time(1), 0}], {1.0/2.0}, G);

	print(std::cerr, G);//, boost::make_label_writer(boost::get(&NodeProperties::leafID, G)), make_edge_writer(boost::get(&EdgeProperties::affinity, G)));

	{
		Eigen::MatrixXd laplacian = getLaplacianNormalized(G);
		const int numCells = laplacian.rows();
		std::cerr << "Segmenting subgraph with " << numCells << " vertices (out of " << boost::num_vertices(G) << ")."
		          << std::endl;
		Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> solver;
		solver.compute(laplacian, Eigen::ComputeEigenvectors);
		Eigen::VectorXd eigenvalues = solver.eigenvalues();
		Eigen::MatrixXd eigenvectors = solver.eigenvectors();
		std::cerr << eigenvalues.transpose() << std::endl;
		std::cerr << eigenvectors << std::endl;
	}

//	{
//		Eigen::SparseMatrix<double> laplacian = getLaplacianSparseNormalized(G);
//		using SparseMatProd = Spectra::SparseGenMatProd<double>; // Spectra::SparseSymMatProd<double>
//		SparseMatProd op(laplacian);
//		Spectra::GenEigsSolver<double, Spectra::LARGEST_MAGN, SparseMatProd> eigs(&op, 3, 5);
//		eigs.init();
//		int nconv = eigs.compute();
//		if (eigs.info() == Spectra::SUCCESSFUL)
//		{
//			std::cerr << nconv << "\n" << eigs.eigenvectors() << std::endl;
//		}
//		else
//		{
//			ROS_WARN("Failed to compute eigenvectors. Clusterign aborted.");
//		}
//	}

	{
		Eigen::SparseMatrix<double> adj = getAdjacencySparse(G);
		mps_msgs::SegmentGraphRequest req;
		for (const auto& triplet : to_triplets(adj))
		{
			std::cerr << triplet.row() << "," << triplet.col() << "," << triplet.value() << std::endl;
			req.adjacency.row_index.push_back(triplet.row());
			req.adjacency.col_index.push_back(triplet.col());
			req.adjacency.value.push_back(triplet.value());
		}
		req.num_labels = 2;

		for (const std::string& alg : std::vector<std::string>{"spectral", "dbscan"})
		{
			req.algorithm = alg;

			mps_msgs::SegmentGraphResponse resp;
			segmentClient.call(req, resp);
			std::cerr << resp << std::endl;

		}
	}

	return 0;
}


// -*- C++ -*-
//
// Package:     Core
// Class  :     FWViewEnergyScale
//
// Implementation:
//     [Notes on implementation]
//
// Original Author:  Alja Mrak-Tadel
//         Created:  Fri Jun 18 20:37:44 CEST 2010
//

#include <stdexcept>
#include <iostream>
#include <boost/bind.hpp>

#include "Rtypes.h"
#include "TMath.h"
#include "Fireworks/Core/interface/FWEveView.h"
#include "Fireworks/Core/interface/FWViewEnergyScale.h"
#include "Fireworks/Core/interface/Context.h"
#include "Fireworks/Core/interface/CmsShowCommon.h"

FWViewEnergyScale::FWViewEnergyScale(std::string name, int version)
    : FWConfigurableParameterizable(version),
      m_scaleMode(this, "ScaleMode", 1l, 1l, 2l),
      m_fixedValToHeight(this, "EnergyToLength [GeV/m]", 50.0, 1.0, 1000.0),
      m_maxTowerHeight(this, "MaximumLength [m]", 3.0, 0.01, 30.0),
      m_plotEt(this, "PlotEt", true),
      m_name(name),
      m_scaleFactor3D(1.f),
      m_scaleFactorLego(0.05f) {
  m_scaleMode.addEntry(kFixedScale, "FixedScale");
  m_scaleMode.addEntry(kAutoScale, "AutomaticScale");
  m_scaleMode.addEntry(kCombinedScale, "CombinedScale");

  m_scaleMode.changed_.connect(boost::bind(&FWViewEnergyScale::scaleParameterChanged, this));
  m_fixedValToHeight.changed_.connect(boost::bind(&FWViewEnergyScale::scaleParameterChanged, this));
  m_maxTowerHeight.changed_.connect(boost::bind(&FWViewEnergyScale::scaleParameterChanged, this));
  m_plotEt.changed_.connect(boost::bind(&FWViewEnergyScale::scaleParameterChanged, this));
}

FWViewEnergyScale::~FWViewEnergyScale() {}

//________________________________________________________

void FWViewEnergyScale::scaleParameterChanged() const { parameterChanged_.emit(); }

float FWViewEnergyScale::calculateScaleFactor(float iMaxVal, bool isLego) const {
  // check if in combined mode
  int mode = m_scaleMode.value();
  if (mode == kCombinedScale) {
    mode = (m_maxTowerHeight.value() > 100 * iMaxVal / m_fixedValToHeight.value()) ? kFixedScale : kAutoScale;
    // printf("COMBINED  \n");
  }
  // get converison

  if (mode == kFixedScale) {
    //  printf("fixed mode %f \n",m_fixedValToHeight.value());
    // apply default constructor height
    float length = isLego ? TMath::Pi() : 100;
    return length / m_fixedValToHeight.value();
  } else {
    float length = isLego ? TMath::Pi() : (100 * m_maxTowerHeight.value());
    // printf("[%d] length %f max %f  \n", isLego, length, iMaxVal);
    return length / iMaxVal;
  }
}

void FWViewEnergyScale::updateScaleFactors(float iMaxVal) {
  m_scaleFactor3D = calculateScaleFactor(iMaxVal, false);
  m_scaleFactorLego = calculateScaleFactor(iMaxVal, true);
}

void FWViewEnergyScale::setFrom(const FWConfiguration& iFrom) {
  for (const_iterator it = begin(), itEnd = end(); it != itEnd; ++it) {
    (*it)->setFrom(iFrom);
  }
}

void FWViewEnergyScale::SetFromCmsShowCommonConfig(long mode, float convert, float maxH, bool et) {
  m_scaleMode.set(mode);
  m_fixedValToHeight.set(convert);
  m_maxTowerHeight.set(maxH);
  m_plotEt.set(et > 0);
}


// Copyright 2020 Dow, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

// Disable select warnings with MSVC and Boost.Geometry 1.66.0
#ifdef _MSC_VER
#pragma warning(disable:4100) // Level 4, C4100: unreferenced formal parameter
#pragma warning(disable:4244) // Level 3, C4244: conversion from 'coordinate_type' to 'promoted_type'
#pragma warning(disable:4267) // Level 3, C4267: conversion from 'size_t' to 'const int'
#pragma warning(disable:4503) // Level 1, C4503: decorated name length exceeded, name was truncated
#endif

#include "GeometryOp.h"

#include <boost/geometry/algorithms/transform.hpp>
#include <boost/geometry/geometries/register/point.hpp>
#include <boost/geometry/geometries/register/ring.hpp>
#include <boost/geometry/geometries/register/multi_polygon.hpp>

#include <boost/log/trivial.hpp>
#include <boost/log/attributes/scoped_attribute.hpp>

// Adapt QPointF to the Boost.Geometry Point Concept
BOOST_GEOMETRY_REGISTER_POINT_2D_GET_SET(QPointF, double, cs::cartesian, x, y, setX, setY)
// Adapt QPolygonF to the Boost.Geometry Ring/Polygon Concept
BOOST_GEOMETRY_REGISTER_RING(QPolygonF)

namespace bg = boost::geometry;

// bg::strategy::buffer::side_straight with extra vertices
class side_straight_ext
{
public:
    explicit inline side_straight_ext(double min_length = 0)
        : m_min_length(min_length)
    {}

    template<typename Point, typename OutputRange, typename DistanceStrategy>
    inline bg::strategy::buffer::result_code apply(
        Point const& input_p1, Point const& input_p2,
        bg::strategy::buffer::buffer_side_selector side,
        DistanceStrategy const& distance,
        OutputRange& output_range) const
    {
        if (bg::math::smaller_or_equals(m_min_length, 0))
            return bg::strategy::buffer::result_no_output;

        // Generate a block along (left or right of) the segment
        double const dx = bg::get<0>(input_p2) - bg::get<0>(input_p1);
        double const dy = bg::get<1>(input_p2) - bg::get<1>(input_p1);

        // For normalization [0,1] (=dot product d.d, sqrt)
        double const length = bg::math::sqrt(dx * dx + dy * dy);

        if (bg::math::equals(length, 0))
            return bg::strategy::buffer::result_no_output;

        double const d = distance.apply(input_p1, input_p2, side);

        // Generate the normalized perpendicular p, to the left (ccw)
        double const px = -dy / length;
        double const py = dx / length;

        // Calculate required number of segments
        std::size_t ns = (std::max)(static_cast<std::size_t>(
            ceil(length / m_min_length)), std::size_t(1));

        // Generate additional vertices
        Point step = input_p2;
        bg::subtract_point(step, input_p1); // input_p1 not modified
        bg::divide_value(step, ns);
        Point accu = input_p1; // accumulator

        output_range.reserve(ns + 1);

        for (std::size_t i = 0; i <= ns; ++i)
        {
            typename boost::range_value<OutputRange>::type output_point;
            bg::set<0>(output_point, bg::get<0>(accu) + px * d);
            bg::set<1>(output_point, bg::get<1>(accu) + py * d);
            output_range.push_back(output_point);
            bg::add_point(accu, step);
        }

        return bg::strategy::buffer::result_normal;
    }

private:
    double m_min_length;
};

GeometryOp::GeometryOp()
{
}

double GeometryOp::area(QPolygonF const& polygon)
{
    return boost::geometry::area(polygon);
}

bool GeometryOp::is_simple(QPolygonF const& polygon)
{
    return boost::geometry::is_simple(polygon);
}

bool GeometryOp::is_valid(QPolygonF const& polygon)
{
    return boost::geometry::is_valid(polygon);
}

bool GeometryOp::correct(QPolygonF &polygon)
{
    BOOST_LOG_SCOPED_THREAD_TAG("Source", "Geometry");

    boost::geometry::correct(polygon);
    std::string message;
    if (!boost::geometry::is_valid(polygon, message)) {
        BOOST_LOG_TRIVIAL(warning) << message;
        return false;
    }
    else {
        return true;
    }
}

QPolygonF GeometryOp::rotate(const QPolygonF& polygon, const QPointF& pivot, double angle)
{
    QPolygonF result;

    bg::strategy::transform::translate_transformer<double, 2, 2> translate1(-pivot.x(), -pivot.y());
    bg::strategy::transform::rotate_transformer<bg::degree, double, 2, 2> rotate(angle);
    bg::strategy::transform::translate_transformer<double, 2, 2> translate2(pivot.x(), pivot.y());

    QPolygonF res1, res2;

    bg::transform(polygon, res1,   translate1); // move from pivot point to origin
    bg::transform(res1,    res2,   rotate);     // rotate about the origin
    bg::transform(res2,    result, translate2); // move from origin to pivot point

    return result;
}

void GeometryOp::rotate(const QPolygonF& polygon, QPolygonF& result, const QPointF& pivot, double angle)
{
    bg::strategy::transform::translate_transformer<double, 2, 2> translate1(-pivot.x(), -pivot.y());
    bg::strategy::transform::rotate_transformer<bg::degree, double, 2, 2> rotate(angle);
    bg::strategy::transform::translate_transformer<double, 2, 2> translate2(pivot.x(), pivot.y());

    QPolygonF res1, res2;

    bg::transform(polygon, res1,   translate1); // move from pivot point to origin
    bg::transform(res1,    res2,   rotate);     // rotate about the origin
    bg::transform(res2,    result, translate2); // move from origin to pivot point
}

void GeometryOp::buffer(std::vector<QPolygonF> const& mpolygon, std::vector<QPolygonF> &result, double distance, double min_length, std::size_t points_per_circle)
{
    mpolygon_t mp_in;
    mpolygon_t mp_out;

    convert(mpolygon, mp_in);
    buffer(mp_in, mp_out, distance, min_length, points_per_circle);
    convert(mp_out, result);
}

void GeometryOp::buffer(mpolygon_t const& mpolygon, mpolygon_t& result, double distance, double min_length, std::size_t points_per_circle)
{
    BOOST_LOG_SCOPED_THREAD_TAG("Source", "Geometry");

    bg::strategy::buffer::distance_symmetric<double> distance_strategy(distance);
    bg::strategy::buffer::join_round join_strategy(points_per_circle);
    bg::strategy::buffer::end_round end_strategy(points_per_circle);
    bg::strategy::buffer::point_circle point_strategy(points_per_circle);
    side_straight_ext side_strategy(min_length); // Replaces bg::strategy::buffer::side_straight

    try {
        bg::buffer(mpolygon, result,
                   distance_strategy, side_strategy, join_strategy, end_strategy, point_strategy);
    } catch (const bg::exception &e) {
        BOOST_LOG_TRIVIAL(error) << e.what();
    }
}

void GeometryOp::convert(mpolygon_t const& mpolygon, std::vector<QPolygonF> &result)
{
    for (polygon_t const& p : mpolygon)
    {
        ring_t const& ring = bg::exterior_ring(p); // outer ring
        QPolygonF qring;
        bg::transform(ring, qring);
        result.push_back(qring);
    }
}

void GeometryOp::convert(std::vector<QPolygonF> const& mpolygon, mpolygon_t& result)
{
    for (QPolygonF const& ring : mpolygon)
    {
        polygon_t p;
        bg::convert(ring, p);
        result.push_back(p);
    }
}


// Copyright (c) 2015-2021 Daniel Cooke
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

#include "cigar_scanner.hpp"

#include <iterator>
#include <algorithm>
#include <numeric>

#include <boost/iterator/zip_iterator.hpp>
#include <boost/tuple/tuple.hpp>

#include "config/common.hpp"
#include "basics/aligned_read.hpp"
#include "basics/cigar_string.hpp"
#include "io/reference/reference_genome.hpp"
#include "concepts/mappable_range.hpp"
#include "utils/mappable_algorithms.hpp"
#include "utils/append.hpp"
#include "utils/sequence_utils.hpp"
#include "utils/free_memory.hpp"
#include "logging/logging.hpp"

#include "utils/maths.hpp"

namespace octopus { namespace coretools {

std::unique_ptr<VariantGenerator> CigarScanner::do_clone() const
{
    return std::make_unique<CigarScanner>(*this);
}

CigarScanner::CigarScanner(const ReferenceGenome& reference, Options options)
: reference_ {reference}
, options_ {options}
, buffer_ {}
, candidates_ {}
, likely_misaligned_candidates_ {}
, max_seen_candidate_size_ {}
, combined_read_coverage_tracker_ {}
, misaligned_read_coverage_tracker_ {}
, sample_read_coverage_tracker_ {}
, sample_forward_strand_coverage_tracker_ {}
, artificial_read_buffer_ {}
{
    buffer_.reserve(100);
}

bool CigarScanner::do_requires_reads() const noexcept
{
    return true;
}

namespace {

template <typename Sequence, typename P, typename S>
Sequence copy(const Sequence& sequence, const P pos, const S size)
{
    const auto it = std::next(std::cbegin(sequence), pos);
    return Sequence {it, std::next(it, size)};
}

double ln_probability_read_correctly_aligned(const double misalign_penalty, const AlignedRead& read,
                                             const double max_expected_mutation_rate)
{
    const auto k = static_cast<unsigned>(std::floor(misalign_penalty));
    if (k == 0) {
        return 0;
    } else {
        const auto ln_prob_missmapped = -maths::constants::ln10Div10<> * read.mapping_quality();
        const auto ln_prob_mapped = std::log(1.0 - std::exp(ln_prob_missmapped));
        const auto mu = max_expected_mutation_rate * region_size(read);
        auto ln_prob_given_mapped = maths::log_poisson_sf(k, mu);
        return ln_prob_mapped + ln_prob_given_mapped;
    }
}

} // namespace

void CigarScanner::do_add_read(const SampleName& sample, const AlignedRead& read)
{
    add_read(sample, read, sample_read_coverage_tracker_[sample], sample_forward_strand_coverage_tracker_[sample]);
}
void CigarScanner::do_add_template(const SampleName& sample, const AlignedTemplate& reads)
{
    add_template(sample, reads, sample_read_coverage_tracker_[sample], sample_forward_strand_coverage_tracker_[sample]);
}

void CigarScanner::add_read(const SampleName& sample, const AlignedRead& read,
                            CoverageTracker<GenomicRegion>& coverage_tracker,
                            CoverageTracker<GenomicRegion>& forward_strand_coverage_tracker)
{
    using std::cbegin; using std::next; using std::move;
    using Flag = CigarOperation::Flag;
    const auto& read_contig   = contig_name(read);
    const auto& read_sequence = read.sequence();
    auto ref_index = mapped_begin(read);
    std::size_t read_index {0};
    GenomicRegion region;
    double misalignment_penalty {0};
    buffer_.clear();
    for (const auto& cigar_operation : read.cigar()) {
        const auto op_size = cigar_operation.size();
        switch (cigar_operation.flag()) {
            case Flag::alignmentMatch:
                misalignment_penalty += add_snvs_in_match_range(GenomicRegion {read_contig, ref_index, ref_index + op_size},
                                                                read, read_index, sample);
                read_index += op_size;
                ref_index  += op_size;
                break;
            case Flag::sequenceMatch:
                read_index += op_size;
                ref_index  += op_size;
                break;
            case Flag::substitution:
            {
                region = GenomicRegion {read_contig, ref_index, ref_index + op_size};
                auto ref_sequence = reference_.get().fetch_sequence(region);
                if (ref_sequence.size() > 1 && options_.split_mnvs) {
                    for (CigarOperation::Size snv_offset {0}; snv_offset < op_size; ++snv_offset) {
                        add_candidate(GenomicRegion {read_contig, ref_index + snv_offset, ref_index + snv_offset + 1},
                                      ref_sequence[snv_offset],
                                      copy(read_sequence, read_index + snv_offset, 1),
                                      read, read_index + snv_offset, sample);
                    }
                } else {
                    add_candidate(std::move(region),
                                  std::move(ref_sequence),
                                  copy(read_sequence, read_index, op_size),
                                  read, read_index, sample);
                }
                read_index += op_size;
                ref_index  += op_size;
                if (options_.misalignment_parameters) misalignment_penalty += op_size * options_.misalignment_parameters->snv_penalty;
                break;
            }
            case Flag::insertion:
            {
                add_candidate(GenomicRegion {read_contig, ref_index, ref_index},
                              "",
                              copy(read_sequence, read_index, op_size),
                              read, read_index, sample);
                read_index += op_size;
                if (options_.misalignment_parameters)  misalignment_penalty += options_.misalignment_parameters->indel_penalty;
                break;
            }
            case Flag::deletion:
            {
                region = GenomicRegion {read_contig, ref_index, ref_index + op_size};
                add_candidate(move(region),
                              reference_.get().fetch_sequence(region),
                              "",
                              read, read_index, sample);
                ref_index += op_size;
                if (options_.misalignment_parameters)  misalignment_penalty += options_.misalignment_parameters->indel_penalty;
                break;
            }
            case Flag::softClipped:
            {
                read_index += op_size;
                ref_index  += op_size;
                if (options_.misalignment_parameters && op_size > options_.misalignment_parameters->max_unpenalised_clip_size) {
                    misalignment_penalty += options_.misalignment_parameters->clip_penalty;
                }
                break;
            }
            case Flag::hardClipped:
            {
                if (options_.misalignment_parameters && op_size > options_.misalignment_parameters->max_unpenalised_clip_size) {
                    misalignment_penalty += options_.misalignment_parameters->clip_penalty;
                }
                break;
            }
            case Flag::padding:
                ref_index += op_size;
                break;
            case Flag::skipped:
                ref_index += op_size;
                break;
        }
    }
    if (options_.use_clipped_coverage_tracking) {
        const auto clipped_region = clipped_mapped_region(read);
        combined_read_coverage_tracker_.add(clipped_region);
        coverage_tracker.add(clipped_region);
        if (is_forward_strand(read)) forward_strand_coverage_tracker.add(clipped_region);
    } else {
        combined_read_coverage_tracker_.add(read);
        coverage_tracker.add(read);
        if (is_forward_strand(read)) forward_strand_coverage_tracker.add(read);
    }
    if (!is_likely_misaligned(read, misalignment_penalty)) {
        utils::append(std::move(buffer_), candidates_);
    } else {
        utils::append(std::move(buffer_), likely_misaligned_candidates_);
        misaligned_read_coverage_tracker_.add(clipped_mapped_region(read));
    }
}

bool have_split_insertion(const AlignedRead& lhs, const AlignedRead& rhs)
{
    return are_adjacent(lhs, rhs) && is_insertion(lhs.cigar().back()) && is_insertion(rhs.cigar().front()) && rhs.cigar().size() == 1;
}

void CigarScanner::add_template(const SampleName& sample, const AlignedTemplate& reads,
                                CoverageTracker<GenomicRegion>& coverage_tracker,
                                CoverageTracker<GenomicRegion>& forward_strand_coverage_tracker)
{
    for (auto read_itr = std::cbegin(reads); read_itr != std::cend(reads);) {
        const static auto has_tail_insertion = [] (const AlignedRead& read) { return is_insertion(read.cigar().back()); };
        const static auto is_all_insertion = [] (const AlignedRead& read) { return read.cigar().size() == 1 && is_insertion(read.cigar().front()); };
        const static auto has_head_insertion = [] (const AlignedRead& read) { return is_insertion(read.cigar().front()); };
        const auto split_insertion_begin_itr = std::find_if(read_itr, std::cend(reads), has_tail_insertion);
        std::for_each(read_itr, split_insertion_begin_itr, [&] (const auto& read) {
            add_read(sample, read, coverage_tracker, forward_strand_coverage_tracker);
        });
        if (split_insertion_begin_itr != std::cend(reads)) {
            auto split_insertion_end_itr = std::find_if_not(std::next(split_insertion_begin_itr), std::cend(reads), is_all_insertion);
            AlignedRead extended_read {*split_insertion_begin_itr};
            std::for_each(std::next(split_insertion_begin_itr), split_insertion_end_itr, [&] (const auto& read) {
                assert(read.cigar().size() == 1);
                utils::append(read.sequence(), extended_read.sequence());
                utils::append(read.base_qualities(), extended_read.base_qualities());
                extended_read.cigar().back().set_size(extended_read.cigar().back().size() + read.cigar().front().size());
            });
            if (split_insertion_end_itr != std::cend(reads) && has_head_insertion(*split_insertion_end_itr)) {
                AlignedRead chopped_read {*split_insertion_end_itr};
                ++split_insertion_end_itr;
                const auto chop_length = chopped_read.cigar().front().size();
                extended_read.cigar().back().set_size(extended_read.cigar().back().size() + chop_length);
                chopped_read.cigar().erase(std::cbegin(chopped_read.cigar()));
                extended_read.sequence().insert(std::cend(extended_read.sequence()), std::cbegin(chopped_read.sequence()), std::next(std::cbegin(chopped_read.sequence()), chop_length));
                chopped_read.sequence().erase(std::cbegin(chopped_read.sequence()), std::next(std::cbegin(chopped_read.sequence()), chop_length));
                extended_read.base_qualities().insert(std::cend(extended_read.base_qualities()), std::cbegin(chopped_read.base_qualities()), std::next(std::cbegin(chopped_read.base_qualities()), chop_length));
                chopped_read.base_qualities().erase(std::cbegin(chopped_read.base_qualities()), std::next(std::cbegin(chopped_read.base_qualities()), chop_length));
                artificial_read_buffer_.push_back(std::move(extended_read));
                add_read(sample, artificial_read_buffer_.back(), coverage_tracker, forward_strand_coverage_tracker);
                artificial_read_buffer_.push_back(std::move(chopped_read));
                add_read(sample, artificial_read_buffer_.back(), coverage_tracker, forward_strand_coverage_tracker);
            } else {
                artificial_read_buffer_.push_back(std::move(extended_read));
                add_read(sample, artificial_read_buffer_.back(), coverage_tracker, forward_strand_coverage_tracker);
            }
            read_itr = split_insertion_end_itr;
        } else {
            break;
        }
    }
}

void CigarScanner::do_add_reads(const SampleName& sample, ReadVectorIterator first, ReadVectorIterator last)
{
    auto& coverage_tracker = sample_read_coverage_tracker_[sample];
    auto& forward_strand_coverage_tracker = sample_forward_strand_coverage_tracker_[sample];
    std::for_each(first, last, [&] (const auto& read) { add_read(sample, read, coverage_tracker, forward_strand_coverage_tracker); });
}
void CigarScanner::do_add_reads(const SampleName& sample, ReadFlatSetIterator first, ReadFlatSetIterator last)
{
    auto& coverage_tracker = sample_read_coverage_tracker_[sample];
    auto& forward_strand_coverage_tracker = sample_forward_strand_coverage_tracker_[sample];
    std::for_each(first, last, [&] (const auto& read) { add_read(sample, read, coverage_tracker, forward_strand_coverage_tracker); });
}
void CigarScanner::do_add_reads(const SampleName& sample, TemplateVectorIterator first, TemplateVectorIterator last)
{
    auto& coverage_tracker = sample_read_coverage_tracker_[sample];
    auto& forward_strand_coverage_tracker = sample_forward_strand_coverage_tracker_[sample];
    std::for_each(first, last, [&] (const auto& reads) { add_template(sample, reads, coverage_tracker, forward_strand_coverage_tracker); });
}
void CigarScanner::do_add_reads(const SampleName& sample, TemplateFlatSetIterator first, TemplateFlatSetIterator last)
{
    auto& coverage_tracker = sample_read_coverage_tracker_[sample];
    auto& forward_strand_coverage_tracker = sample_forward_strand_coverage_tracker_[sample];
    std::for_each(first, last, [&] (const auto& reads) { add_template(sample, reads, coverage_tracker, forward_strand_coverage_tracker); });
}

unsigned get_min_depth(const Variant& v, const CoverageTracker<GenomicRegion>& tracker)
{
    if (is_insertion(v)) {
        const auto& region = mapped_region(v);
        if (region.begin() > 0) {
            return tracker.min(expand(region, 1, 1));
        } else {
            return tracker.min(expand_rhs(region, 1));
        }
    } else {
        return tracker.min(mapped_region(v));
    }
}

struct VariantBucket : public Mappable<VariantBucket>
{
    VariantBucket(GenomicRegion region) : region {std::move(region)} {}
    GenomicRegion region;
    std::deque<Variant> variants;
    const GenomicRegion& mapped_region() const noexcept { return region; }
};

auto init_variant_buckets(const std::vector<GenomicRegion>& regions)
{
    std::vector<VariantBucket> result {};
    result.reserve(regions.size());
    std::transform(std::cbegin(regions), std::cend(regions), std::back_inserter(result),
                   [] (const auto& region) { return VariantBucket {region}; });
    return result;
}

boost::optional<VariantBucket&> find_contained(std::vector<VariantBucket>& buckets, const Variant& variant)
{
    if (buckets.empty()) return boost::none;
    const auto itr = std::find_if(std::begin(buckets), std::end(buckets),
                                  [&] (const auto& region) { return contains(region, variant); });
    if (itr != std::end(buckets)) {
        assert(contains(*itr, variant));
        return *itr;
    } else {
        return boost::none;
    }
}

void choose_push_back(Variant candidate, std::vector<Variant>& final_candidates,
                      std::vector<VariantBucket>& repeat_buckets)
{
    auto bucket = find_contained(repeat_buckets, candidate);
    if (bucket) {
        bucket->variants.push_back(std::move(candidate));
    } else {
        final_candidates.push_back(std::move(candidate));
    }
}

std::vector<Variant> CigarScanner::do_generate(const RegionSet& regions) const
{
    std::sort(std::begin(candidates_), std::end(candidates_));
    std::sort(std::begin(likely_misaligned_candidates_), std::end(likely_misaligned_candidates_));
    std::vector<Variant> result {};
    for (const auto& region : regions) {
        generate(region, result);
    }
    return result;
}

void CigarScanner::do_clear() noexcept
{
    free_memory(buffer_);
    free_memory(candidates_);
    free_memory(likely_misaligned_candidates_);
    free_memory(combined_read_coverage_tracker_);
    free_memory(misaligned_read_coverage_tracker_);
    free_memory(sample_read_coverage_tracker_);
    free_memory(sample_forward_strand_coverage_tracker_);
    free_memory(artificial_read_buffer_);
    max_seen_candidate_size_ = 0;
}

std::string CigarScanner::name() const
{
    return "CigarScanner";
}

// private methods

double CigarScanner::add_snvs_in_match_range(const GenomicRegion& region, const AlignedRead& read,
                                             std::size_t read_index, const SampleName& origin)
{
    const NucleotideSequence ref_segment {reference_.get().fetch_sequence(region)};
    double misalignment_penalty {0};
    for (std::size_t ref_index {0}; ref_index < ref_segment.size(); ++ref_index, ++read_index) {
        const char ref_base {ref_segment[ref_index]}, read_base {read.sequence()[read_index]};
        if (ref_base != read_base && ref_base != 'N' && read_base != 'N') {
            const auto begin_pos = region.begin() + static_cast<GenomicRegion::Position>(ref_index);
            add_candidate(GenomicRegion {region.contig_name(), begin_pos, begin_pos + 1},
                          ref_base, read_base, read, read_index, origin);
            if (options_.misalignment_parameters && read.base_qualities()[read_index] >= options_.misalignment_parameters->snv_threshold) {
                misalignment_penalty += options_.misalignment_parameters->snv_penalty;
            }
        }
    }
    return misalignment_penalty;
}

void CigarScanner::generate(const GenomicRegion& region, std::vector<Variant>& result) const
{
    using std::begin; using std::end; using std::cbegin; using std::cend; using std::next;
    assert(std::is_sorted(std::cbegin(candidates_), std::cend(candidates_)));
    auto viable_candidates = overlap_range(candidates_, region, max_seen_candidate_size_);
    if (empty(viable_candidates)) return;
    result.reserve(result.size() + size(viable_candidates, BidirectionallySortedTag {})); // maximum possible
    const auto last_viable_candidate_itr = cend(viable_candidates);
    while (!viable_candidates.empty()) {
        const Candidate& candidate {viable_candidates.front()};
        const auto next_candidate_itr = std::find_if_not(next(cbegin(viable_candidates)), last_viable_candidate_itr,
                                                         [this, &candidate] (const Candidate& c) {
                                                             return options_.match(c.variant, candidate.variant);
                                                         });
        const auto num_matches = std::distance(cbegin(viable_candidates), next_candidate_itr);
        const auto observation = make_observation(cbegin(viable_candidates), next_candidate_itr);
        if (options_.include(observation)) {
            if (num_matches > 1) {
                auto unique_itr = cbegin(viable_candidates);
                while (unique_itr != next_candidate_itr) {
                    result.push_back(unique_itr->variant);
                    unique_itr = std::find_if_not(next(unique_itr), next_candidate_itr,
                                                  [unique_itr] (const Candidate& c) {
                                                      return c.variant == unique_itr->variant;
                                                  });
                }
            } else {
                result.push_back(candidate.variant);
            }
        }
        viable_candidates.advance_begin(num_matches);
    }
    if (debug_log_ && !likely_misaligned_candidates_.empty()) {
        const auto novel_unique_misaligned_variants = get_novel_likely_misaligned_candidates(result);
        if (!novel_unique_misaligned_variants.empty()) {
            stream(*debug_log_) << "DynamicCigarScanner: ignoring "
                                << count_overlapped(novel_unique_misaligned_variants, region)
                                << " unique candidates in " << region;
        }
    }
}

unsigned CigarScanner::sum_base_qualities(const Candidate& candidate) const noexcept
{
    const auto first_base_qual_itr = std::next(std::cbegin(candidate.source.get().base_qualities()), candidate.offset);
    const auto last_base_qual_itr = std::next(first_base_qual_itr, alt_sequence_size(candidate.variant));
    return std::accumulate(first_base_qual_itr, last_base_qual_itr, 0u);
}

bool CigarScanner::is_likely_misaligned(const AlignedRead& read, const double penalty) const
{
    if (options_.misalignment_parameters) {
        auto mu = options_.misalignment_parameters->max_expected_mutation_rate;
        auto ln_prob_misaligned = ln_probability_read_correctly_aligned(penalty, read, mu);
        return ln_prob_misaligned < options_.misalignment_parameters->min_ln_prob_correctly_aligned;
    } else {
        return false;
    }
}

CigarScanner::VariantObservation
CigarScanner::make_observation(const CandidateIterator first_match, const CandidateIterator last_match) const
{
    assert(first_match != last_match);
    const Candidate& candidate {*first_match};
    VariantObservation result {};
    result.variant = candidate.variant;
    result.total_depth = get_min_depth(candidate.variant, combined_read_coverage_tracker_);
    std::vector<Candidate> observations {first_match, last_match};
    std::sort(begin(observations), end(observations),
              [] (const Candidate& lhs, const Candidate& rhs) { return lhs.origin.get() < rhs.origin.get(); });
    for (auto observation_itr = begin(observations); observation_itr != end(observations);) {
        const auto& origin = observation_itr->origin;
        auto next_itr = std::find_if_not(next(observation_itr), end(observations),
                                         [&] (const Candidate& c) { return c.origin.get() == origin.get(); });
        const auto num_observations = static_cast<unsigned>(std::distance(observation_itr, next_itr));
        std::vector<unsigned> observed_base_qualities(num_observations);
        std::transform(observation_itr, next_itr, begin(observed_base_qualities),
                       [this] (const Candidate& c) noexcept { return sum_base_qualities(c); });
        std::vector<AlignedRead::MappingQuality> observed_mapping_qualities(num_observations);
        std::transform(observation_itr, next_itr, begin(observed_mapping_qualities),
                       [] (const Candidate& c) noexcept { return c.source.get().mapping_quality(); });
        const auto forward_strand_support = std::accumulate(observation_itr, next_itr, 0u,
                                                     [] (unsigned curr, const Candidate& c) noexcept {
                                                         if (is_forward_strand(c.source.get())) {
                                                             ++curr;
                                                         }
                                                         return curr;
                                                     });
        const auto edge_support = std::accumulate(observation_itr, next_itr, 0u,
                                                      [] (unsigned curr, const Candidate& c) noexcept {
                                                          if (begins_equal(c, c.source.get()) || ends_equal(c, c.source.get())) {
                                                              ++curr;
                                                          }
                                                          return curr;
                                                      });
        const auto depth = std::max(get_min_depth(candidate.variant, sample_read_coverage_tracker_.at(origin)), num_observations);
        const auto forward_depth = get_min_depth(candidate.variant, sample_forward_strand_coverage_tracker_.at(origin));
        result.sample_observations.push_back({origin, depth, forward_depth,
                                              std::move(observed_base_qualities),
                                              std::move(observed_mapping_qualities),
                                              forward_strand_support, edge_support});
        observation_itr = next_itr;
    }
    return result;
}

std::vector<Variant>
CigarScanner::get_novel_likely_misaligned_candidates(const std::vector<Variant>& current_candidates) const
{
    std::is_sorted(std::cbegin(likely_misaligned_candidates_), std::cend(likely_misaligned_candidates_));
    std::vector<Candidate> unique_misaligned_candidates {};
    unique_misaligned_candidates.reserve(likely_misaligned_candidates_.size());
    std::unique_copy(std::cbegin(likely_misaligned_candidates_), std::cend(likely_misaligned_candidates_),
                     std::back_inserter(unique_misaligned_candidates));
    std::vector<Variant> unique_misaligned_variants {};
    unique_misaligned_variants.reserve(unique_misaligned_candidates.size());
    std::transform(std::cbegin(unique_misaligned_candidates), std::cend(unique_misaligned_candidates),
                   std::back_inserter(unique_misaligned_variants),
                   [] (const Candidate& candidate) { return candidate.variant; });
    std::vector<Variant> result {};
    result.reserve(unique_misaligned_variants.size());
    assert(std::is_sorted(std::cbegin(current_candidates), std::cend(current_candidates)));
    std::set_difference(std::cbegin(unique_misaligned_variants), std::cend(unique_misaligned_variants),
                        std::cbegin(current_candidates), std::cend(current_candidates),
                        std::back_inserter(result));
    return result;
}

// non-member methods

namespace {

struct StrandSupportStats
{
    unsigned forward_support, forward_depth, reverse_support, reverse_depth;
};

auto sum(const std::vector<unsigned>& observed_qualities) noexcept
{
    return std::accumulate(std::cbegin(observed_qualities), std::cend(observed_qualities), 0);
}

void erase_below(std::vector<unsigned>& observed_qualities, const unsigned min)
{
    observed_qualities.erase(std::remove_if(std::begin(observed_qualities), std::end(observed_qualities),
                                            [=] (const auto q) { return q < min; }),
                             std::end(observed_qualities));
}

void partial_sort(std::vector<unsigned>& observed_qualities, const unsigned n)
{
    std::partial_sort(std::begin(observed_qualities), std::next(std::begin(observed_qualities), n),
                      std::end(observed_qualities), std::greater<> {});
}

double compute_strand_bias(const StrandSupportStats& strand_depths)
{
    return 1 - maths::fisher_exact_test(strand_depths.forward_support, strand_depths.forward_depth - strand_depths.forward_support,
                                        strand_depths.reverse_support, strand_depths.reverse_depth - strand_depths.reverse_support);
}

bool only_observed_on_one_strand(const StrandSupportStats& strand_depths) noexcept
{
    const auto support = strand_depths.forward_support + strand_depths.reverse_support;
    return support > 0 && (strand_depths.forward_support == 0 || strand_depths.reverse_support == support);
}

bool is_likely_runthrough_artifact(const StrandSupportStats& strand_depths, std::vector<unsigned>& observed_qualities)
{
    const auto num_observations = strand_depths.forward_support + strand_depths.reverse_support;
    if (num_observations < 10 || !only_observed_on_one_strand(strand_depths)) return false;
    assert(!observed_qualities.empty());
    const auto median_bq = maths::median(observed_qualities);
    return median_bq < 15;
}

bool is_tandem_repeat(const Allele& allele, const unsigned max_period = 4)
{
    for (unsigned period {0}; period <= max_period; ++period) {
        if (utils::is_tandem_repeat(allele.sequence(), period)) return true;
    }
    return false;
}

bool is_good_germline(const Variant& variant, const unsigned depth, const unsigned forward_strand_depth,
                      const unsigned forward_strand_support, std::vector<unsigned> observed_qualities,
                      const unsigned copy_number = 2)
{
    const auto support = observed_qualities.size();
    if (depth < 4) {
        return support > 1 || sum(observed_qualities) >= 30 || is_deletion(variant);
    }
    const StrandSupportStats strand_depths {forward_strand_support,
                                            forward_strand_depth,
                                            static_cast<unsigned>(support) - forward_strand_support,
                                            depth - forward_strand_depth};
    const auto strand_bias = compute_strand_bias(strand_depths);
    if (support > 20 && strand_bias > 0.99 && only_observed_on_one_strand(strand_depths)) return false;
    if (is_snv(variant)) {
        if (is_likely_runthrough_artifact(strand_depths, observed_qualities)) return false;
        erase_below(observed_qualities, 20);
        if (depth <= 10) return observed_qualities.size() > 1;
        return observed_qualities.size() > 2 && static_cast<double>(observed_qualities.size()) / depth > (1. / (5 * copy_number));
    } else if (is_insertion(variant)) {
        if (support == 1 && alt_sequence_size(variant) > 10) return false;
        if (depth < 10) {
            return support > 1 || (alt_sequence_size(variant) > 3 && is_tandem_repeat(variant.alt_allele()));
        } else if (depth <= 30) {
            return support > 1;
        } else if (depth <= 60) {
            if (support == 1) return false;
            if (static_cast<double>(support) / depth > 0.3) return true;
            erase_below(observed_qualities, 25);
            if (observed_qualities.size() <= 1) return false;
            if (observed_qualities.size() > 2) return true;
            partial_sort(observed_qualities, 2);
            return static_cast<double>(observed_qualities[0]) / alt_sequence_size(variant) > 20;
        } else {
            if (support == 1) return false;
            if (static_cast<double>(support) / depth > 0.35) return true;
            erase_below(observed_qualities, 20);
            if (observed_qualities.size() <= 1) return false;
            if (observed_qualities.size() > 3) return true;
            return static_cast<double>(observed_qualities[0]) / alt_sequence_size(variant) > 20;
        }
    } else {
        // deletion or mnv
        if (region_size(variant) < 10) {
            return support > 1 && static_cast<double>(support) / depth > (1. / (10 * copy_number));
        } else {
            return static_cast<double>(support) / (depth - std::sqrt(depth)) > (1. / (5 * copy_number));
        }
    }
}

struct UnknownExpectedVAFStats
{
    double min_vaf, min_probability;
};

auto beta_sf(unsigned a, unsigned b, double x)
{
    // Haldane's prior but make sure is proper
    return maths::beta_sf(static_cast<double>(std::max(a, 1u)), static_cast<double>(std::max(b, 1u)), x);
}

bool is_good_somatic(const Variant& variant, const unsigned depth, const unsigned forward_strand_depth,
                     const unsigned forward_strand_support, const unsigned num_edge_observations,
                     std::vector<unsigned> observed_qualities, const UnknownExpectedVAFStats vaf_def)
{
    assert(depth > 0);
    const auto support = observed_qualities.size();
    const StrandSupportStats strand_depths {forward_strand_support,
                                            forward_strand_depth,
                                            static_cast<unsigned>(support) - forward_strand_support,
                                            depth - forward_strand_depth};
    const auto strand_bias = compute_strand_bias(strand_depths);
    if (support > 10 && strand_bias > 0.99 && only_observed_on_one_strand(strand_depths)) return false;
    if (is_snv(variant)) {
        if (is_likely_runthrough_artifact(strand_depths, observed_qualities)) return false;
        erase_below(observed_qualities, 20);
        if (observed_qualities.size() <= num_edge_observations) return false;
        const auto good_support = observed_qualities.size() - num_edge_observations;
        const auto probability_vaf_greater_than_min_vaf = beta_sf(good_support, depth - good_support, vaf_def.min_vaf);
        return good_support > 1
            && probability_vaf_greater_than_min_vaf >= vaf_def.min_probability
            && num_edge_observations < support;
    } else if (is_insertion(variant)) {
        if (support == 1 && alt_sequence_size(variant) > 8) return false;
        erase_below(observed_qualities, 20);
        const auto good_support = observed_qualities.size();
        if (good_support > 1 && alt_sequence_size(variant) > 10) return true;
        const auto probability_vaf_greater_than_min_vaf = beta_sf(good_support, depth - good_support, vaf_def.min_vaf);
        return good_support > 1 && probability_vaf_greater_than_min_vaf >= vaf_def.min_probability;
    } else {
        // deletion or mnv
        const auto probability_vaf_greater_than_min_vaf = beta_sf(support, depth - support, vaf_def.min_vaf);
        return support > 1 && probability_vaf_greater_than_min_vaf >= vaf_def.min_probability;
    }
}

bool is_good_germline(const Variant& v, const CigarScanner::VariantObservation::SampleObservationStats& observation,
                      unsigned ploidy = 2)
{
    return is_good_germline(v, observation.depth, observation.forward_strand_depth,
                            observation.forward_strand_support, observation.observed_base_qualities,
                            ploidy);
}

bool any_good_germline_samples(const CigarScanner::VariantObservation& candidate, unsigned ploidy = 2)
{
    return std::any_of(std::cbegin(candidate.sample_observations), std::cend(candidate.sample_observations),
                       [&] (const auto& observation) { return is_good_germline(candidate.variant, observation, ploidy); });
}

auto count_forward_strand_depth(const CigarScanner::VariantObservation& candidate)
{
    return std::accumulate(std::cbegin(candidate.sample_observations), std::cend(candidate.sample_observations), 0u,
                           [&] (auto curr, const auto& observation) { return curr + observation.forward_strand_depth; });
}

auto count_forward_strand_support(const CigarScanner::VariantObservation& candidate)
{
    return std::accumulate(std::cbegin(candidate.sample_observations), std::cend(candidate.sample_observations), 0u,
                           [&] (auto curr, const auto& observation) { return curr + observation.forward_strand_support; });
}

auto count_edge_support(const CigarScanner::VariantObservation& candidate)
{
    return std::accumulate(std::cbegin(candidate.sample_observations), std::cend(candidate.sample_observations), 0u,
                           [&] (auto curr, const auto& observation) { return curr + observation.edge_support; });
}

auto concat_observed_base_qualities(const CigarScanner::VariantObservation& candidate)
{
    std::size_t num_base_qualities {0};
    for (const auto& observation : candidate.sample_observations) {
        num_base_qualities += observation.observed_base_qualities.size();
    }
    std::vector<unsigned> result {};
    result.reserve(num_base_qualities);
    for (const auto& observation : candidate.sample_observations) {
        utils::append(observation.observed_base_qualities, result);
    }
    return result;
}

bool is_good_germline_pooled(const CigarScanner::VariantObservation& candidate)
{
    return is_good_germline(candidate.variant, candidate.total_depth, count_forward_strand_depth(candidate),
                            count_forward_strand_support(candidate), concat_observed_base_qualities(candidate));
}

bool is_good_somatic(const Variant& v, const CigarScanner::VariantObservation::SampleObservationStats& observation,
                     UnknownExpectedVAFStats vaf_def)
{
    return is_good_somatic(v, observation.depth, observation.forward_strand_depth, observation.forward_strand_support,
                           observation.edge_support, observation.observed_base_qualities, vaf_def);
}

bool is_good_somatic_pooled(const CigarScanner::VariantObservation& candidate, const UnknownExpectedVAFStats& vaf_def)
{
    return is_good_somatic(candidate.variant, candidate.total_depth, count_forward_strand_depth(candidate),
                           count_forward_strand_support(candidate), count_edge_support(candidate),
                           concat_observed_base_qualities(candidate), vaf_def);
}

bool is_good_pacbio(const Variant& variant, const unsigned depth, const unsigned forward_strand_depth,
                    const unsigned forward_strand_support, std::vector<unsigned> observed_qualities)
{
    const auto support = observed_qualities.size();
    const auto vaf = static_cast<double>(support) / depth;
    if (is_snv(variant)) {
        return support > 1 && vaf > 0.1;
    } else if (is_insertion(variant)) {
        if (alt_sequence_size(variant) > 500) {
            return true;
        } else if (alt_sequence_size(variant) > 200) {
            return vaf > 0.02;
        } else if (alt_sequence_size(variant) > 20) {
            return vaf > 0.05;
        }
        if (support < 2) return false;
        if (alt_sequence_size(variant) <= 2) {
            return vaf > 0.2;
        } else if (alt_sequence_size(variant) < 4) {
            return vaf > 0.1;
        } else {
            return vaf > 0.05;
        }
    } else { // deletion or mnv
        if (region_size(variant) > 50) {
            return vaf > 0.1;
        }
        if (support < 2) return false;
        if (region_size(variant) <= 2) {
            return vaf > 0.2;
        } else if (region_size(variant) < 4) {
            return vaf > 0.1;
        } else {
            return vaf > 0.05;
        }
    }
}

bool is_good_pacbio(const Variant& v, const CigarScanner::VariantObservation::SampleObservationStats& observation)
{
    return is_good_pacbio(v, observation.depth, observation.forward_strand_depth,
                          observation.forward_strand_support, observation.observed_base_qualities);
}

bool any_good_pacbio_samples(const CigarScanner::VariantObservation& candidate)
{
    return std::any_of(std::cbegin(candidate.sample_observations), std::cend(candidate.sample_observations),
                       [&] (const auto& observation) { return is_good_pacbio(candidate.variant, observation); });
}

bool is_good_pacbio_pooled(const CigarScanner::VariantObservation& candidate)
{
    return is_good_pacbio(candidate.variant, candidate.total_depth, count_forward_strand_depth(candidate),
                          count_forward_strand_support(candidate), concat_observed_base_qualities(candidate));
}

} // namespace

bool KnownCopyNumberInclusionPredicate::operator()(const CigarScanner::VariantObservation& candidate)
{
    return any_good_germline_samples(candidate, copy_number_) || (candidate.sample_observations.size() > 1 && is_good_germline_pooled(candidate));
}

bool PacBioInclusionPredicate::operator()(const CigarScanner::VariantObservation& candidate)
{
    return any_good_pacbio_samples(candidate) || (candidate.sample_observations.size() > 1 && is_good_pacbio_pooled(candidate));
}

UnknownCopyNumberInclusionPredicate::UnknownCopyNumberInclusionPredicate(double min_vaf, double min_probability)
: normal_ {}
, min_vaf_ {min_vaf}
, min_probability_ {min_probability}
{}

UnknownCopyNumberInclusionPredicate::UnknownCopyNumberInclusionPredicate(SampleName normal, double min_vaf, double min_probability)
: normal_ {std::move(normal)}
, min_vaf_ {min_vaf}
, min_probability_ {min_probability}
{}

bool UnknownCopyNumberInclusionPredicate::operator()(const CigarScanner::VariantObservation& candidate)
{
    const UnknownExpectedVAFStats vaf_def {min_vaf_, min_probability_};
    return std::any_of(std::cbegin(candidate.sample_observations), std::cend(candidate.sample_observations),
                       [&] (const auto& observation) {
                           if (normal_ && observation.sample.get() == *normal_) {
                               return is_good_germline(candidate.variant, observation);
                           } else {
                               return is_good_somatic(candidate.variant, observation, vaf_def);
                           }
                       });
}

bool is_good_cell(const Variant& v, const CigarScanner::VariantObservation::SampleObservationStats& observation)
{
    const UnknownExpectedVAFStats vaf_def {0.2, 0.5};
    return is_good_somatic(v, observation, vaf_def);
}

bool any_good_cell_samples(const CigarScanner::VariantObservation& candidate)
{
    return std::any_of(std::cbegin(candidate.sample_observations), std::cend(candidate.sample_observations),
                       [&] (const auto& observation) { return is_good_cell(candidate.variant, observation); });
}

bool is_good_cell_pooled(const CigarScanner::VariantObservation& candidate)
{
    const UnknownExpectedVAFStats vaf_def {0.2, 0.8};
    return is_good_somatic_pooled(candidate, vaf_def);
}

bool CellInclusionPredicate::operator()(const CigarScanner::VariantObservation& candidate)
{
    return any_good_cell_samples(candidate) || (candidate.sample_observations.size() > 1 && is_good_cell_pooled(candidate));
}

namespace {

auto count_observations(const CigarScanner::VariantObservation& candidate)
{
    return std::accumulate(std::cbegin(candidate.sample_observations), std::cend(candidate.sample_observations), std::size_t {0},
                           [] (auto curr, const auto& sample) { return curr + sample.observed_base_qualities.size(); });
}

} // namespace

bool SimpleThresholdInclusionPredicate::operator()(const CigarScanner::VariantObservation& candidate) noexcept
{
    return count_observations(candidate) >= min_observations_;
}

bool TolerantMatchPredicate::operator()(const Variant& lhs, const Variant& rhs) noexcept
{
    if (!are_same_type(lhs, rhs) || is_snv(lhs) || is_mnv(lhs)) {
        return lhs == rhs;
    }
    if (is_insertion(lhs)) {
        if (!is_same_region(lhs, rhs)) return false;
        if (alt_sequence_size(lhs) == alt_sequence_size(rhs)) {
            const auto& lhs_alt = alt_sequence(lhs);
            const auto& rhs_alt = alt_sequence(rhs);
            return std::count(std::cbegin(lhs_alt), std::cend(lhs_alt), 'N')
                   == std::count(std::cbegin(rhs_alt), std::cend(rhs_alt), 'N');
        } else {
            if (utils::is_homopolymer(alt_sequence(lhs)) && utils::is_homopolymer(alt_sequence(rhs))) {
                return alt_sequence(lhs).front() == alt_sequence(rhs).front();
            } else {
                return false;
            }
        }
    }
    return overlaps(lhs, rhs);
}

} // coretools
} // namespace octopus


#include "CryptoUtils.h"
#include <boost/algorithm/hex.hpp>

#include <boost/uuid/detail/md5.hpp>
#include <boost/uuid/uuid.hpp>

#include <boost/uuid/random_generator.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <boost/lexical_cast.hpp>
#include "StringUtils.h"

using namespace wlmatrix;
using boost::uuids::detail::md5;

    std::string CryptoUtils::hashMD5(std::string value){
        md5 hash;
        md5::digest_type digest;

        hash.process_bytes(value.data(), value.size());
        hash.get_digest(digest);

        const auto charDigest = reinterpret_cast<const char *>(&digest);
        std::string result;
        boost::algorithm::hex_lower(charDigest, charDigest + sizeof(md5::digest_type), std::back_inserter(result));
        return result;
    };

    std::string CryptoUtils::getMD5uuid(std::string value){
        auto md5 = CryptoUtils::hashMD5(value);
        std::string uuid = md5.substr(0,8) + "-" + md5.substr(8,4) + "-" + md5.substr(12, 4) + "-" + md5.substr(16, 4)+ "-" + md5.substr(20, 12);
        return uuid;
    }

    std::string CryptoUtils::getUuidAsDecimal(std::string uuid) {
        std::string trimmed = uuid.substr(19,4) + uuid.substr(24,12);
        std::string test = boost::algorithm::hex(trimmed);
        return test;
    }

    std::string CryptoUtils::getRandomUuid() {
        boost::uuids::random_generator gen;
        boost::uuids::uuid randomUuid = gen();
        return boost::uuids::to_string(randomUuid);;
    }

    std::string CryptoUtils::UUIDFromStr(std::string source){
        boost::uuids::nil_generator genNil;
        boost::uuids::uuid nil = genNil();
        boost::uuids::name_generator gen(nil);
        boost::uuids::uuid uuid = gen(source);
        return boost::uuids::to_string(uuid);
    }


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

  @copyright 2016 NumScale SAS

  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_SHL_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_SHL_HPP_INCLUDED

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

 /*!

    @ingroup group-operator
    Function object implementing shl capabilities

    return left shift of the first operand by the second
    that must be of integer type and of the same number
    of elements as the first parameter
    Infix notation can be used with operator '<<'

    This is a convenience alias of @ref shift_left
  **/
  Value shl(Value const & v0, IntegerValue const& n);
} }
#endif

#include <boost/simd/function/scalar/shift_left.hpp>
#include <boost/simd/function/simd/shl.hpp>

#endif


#include "../include/common.h"
#include "../include/frame.h"
#include "../include/camera.h"
#include "../include/visual_odometry.h"
#include "../include/viewer.h"
#include <Eigen/src/Core/Matrix.h>
#include <opencv2/core/types.hpp>
#include <opencv2/highgui.hpp>
#include <vector>
#include <thread>

void LoadImg(const string& strParaFile, vector<string>& img, vector<string>& timeStamp, const char& camera);

int main(int argc, char* argv[])
{
	//read parameter and image
	if(argc != 2)
	{
		cout << "wrong parameter" << endl;
		return 1;
	}

	std::vector<string> strImgLeft, strImgRight, timeStampLeft, timeStampRight;
	LoadImg(argv[1], strImgLeft, timeStampLeft, 'L');
	LoadImg(argv[1], strImgRight, timeStampRight, 'R');
	size_t frameNum = strImgLeft.size();
	cout << "----load " << frameNum << " frames" <<endl;

	Simple_ORB_SLAM::Camera euroc(argv[1]);
	Simple_ORB_SLAM::Map map;
	Simple_ORB_SLAM::LocalMapping localMapper(&map);
	Simple_ORB_SLAM::VisualOdometry vo(&localMapper, &euroc, &map);
	Simple_ORB_SLAM::Viewer viewer(&euroc, &map, &vo);

	std::thread* tViewer = new std::thread(&Simple_ORB_SLAM::Viewer::Run, &viewer);
	std::thread* tMapper = new std::thread(&Simple_ORB_SLAM::LocalMapping::Run, &localMapper);
	for(size_t i=0; i<frameNum; i++)
	{
		cv::Mat imgLeft, imgRight;
		cv::Mat imgLeftRec, imgRightRec;

		imgLeft = cv::imread(strImgLeft[i]);
		imgRight = cv::imread(strImgRight[i]);

		if(imgLeft.empty() || imgRight.empty())
		{
			cout << "----empty image" << endl;
			return 1;
		}

		euroc.RectifyL(imgLeft, imgLeftRec);
		euroc.RectifyR(imgRight, imgRightRec);

		cvtColor(imgLeftRec,imgLeftRec,CV_RGB2GRAY);
        cvtColor(imgRightRec,imgRightRec,CV_RGB2GRAY);

		vo.Run(timeStampLeft[i], imgLeftRec, imgRightRec);

	}

	return 0;
}

void LoadImg(const string& strParaFile, vector<string>& img, vector<string>& timeStamp, const char& camera)
{
	string img_name = strParaFile;
	if(camera == 'L')
	{
		img_name += "cam0/data.csv";
	}
	else
	{
		img_name += "cam1/data.csv";
	}

	string oneline,part_of_str;
	vector<string> line_of_str;
	bool is_fist_row = true;

	ifstream img_input(img_name.c_str());
	while(getline(img_input, oneline))
	{
		istringstream read_str(oneline);
		if(is_fist_row == true )
		{
			is_fist_row = false;
			continue;
		}

		while(getline(read_str, part_of_str, ','))
		{
			part_of_str.erase(part_of_str.end()-1);
			line_of_str.push_back(part_of_str);
		}
	}

	for(size_t i=0; i<line_of_str.size(); i+=2 )
	{
		if(camera == 'L')
			img.push_back(strParaFile + "cam0/data/" +line_of_str[i+1]);
		else
			img.push_back(strParaFile + "cam1/data/" + line_of_str[i+1]);

		timeStamp.push_back(line_of_str[i]);
	}
}


#ifndef STAN_MATH_PRIM_PROB_FRECHET_CDF_HPP
#define STAN_MATH_PRIM_PROB_FRECHET_CDF_HPP

#include <stan/math/prim/meta.hpp>
#include <stan/math/prim/err.hpp>
#include <stan/math/prim/fun/constants.hpp>
#include <stan/math/prim/fun/exp.hpp>
#include <stan/math/prim/fun/log.hpp>
#include <stan/math/prim/fun/log1m.hpp>
#include <stan/math/prim/fun/max_size.hpp>
#include <stan/math/prim/fun/multiply_log.hpp>
#include <stan/math/prim/fun/scalar_seq_view.hpp>
#include <stan/math/prim/fun/size.hpp>
#include <stan/math/prim/fun/size_zero.hpp>
#include <stan/math/prim/fun/to_ref.hpp>
#include <stan/math/prim/fun/value_of.hpp>
#include <boost/random/weibull_distribution.hpp>
#include <boost/random/variate_generator.hpp>
#include <stan/math/prim/functor/operands_and_partials.hpp>
#include <cmath>

namespace stan {
namespace math {

template <typename T_y, typename T_shape, typename T_scale,
          require_all_not_nonscalar_prim_or_rev_kernel_expression_t<
              T_y, T_shape, T_scale>* = nullptr>
return_type_t<T_y, T_shape, T_scale> frechet_cdf(const T_y& y,
                                                 const T_shape& alpha,
                                                 const T_scale& sigma) {
  using T_partials_return = partials_return_t<T_y, T_shape, T_scale>;
  using T_y_ref = ref_type_t<T_y>;
  using T_alpha_ref = ref_type_t<T_shape>;
  using T_sigma_ref = ref_type_t<T_scale>;
  using std::pow;
  static const char* function = "frechet_cdf";
  T_y_ref y_ref = y;
  T_alpha_ref alpha_ref = alpha;
  T_sigma_ref sigma_ref = sigma;

  check_positive(function, "Random variable", y_ref);
  check_positive_finite(function, "Shape parameter", alpha_ref);
  check_positive_finite(function, "Scale parameter", sigma_ref);

  if (size_zero(y_ref, alpha_ref, sigma_ref)) {
    return 1.0;
  }

  T_partials_return cdf(1.0);
  operands_and_partials<T_y_ref, T_alpha_ref, T_sigma_ref> ops_partials(
      y_ref, alpha_ref, sigma_ref);

  scalar_seq_view<T_y> y_vec(y_ref);
  scalar_seq_view<T_scale> sigma_vec(sigma_ref);
  scalar_seq_view<T_shape> alpha_vec(alpha_ref);
  size_t N = max_size(y_ref, sigma_ref, alpha_ref);

  for (size_t n = 0; n < N; n++) {
    const T_partials_return y_dbl = y_vec.val(n);
    const T_partials_return sigma_dbl = sigma_vec.val(n);
    const T_partials_return alpha_dbl = alpha_vec.val(n);
    const T_partials_return pow_n = pow(sigma_dbl / y_dbl, alpha_dbl);
    const T_partials_return cdf_n = exp(-pow_n);

    cdf *= cdf_n;

    if (!is_constant_all<T_y>::value) {
      ops_partials.edge1_.partials_[n] += pow_n * alpha_dbl / y_dbl;
    }
    if (!is_constant_all<T_shape>::value) {
      ops_partials.edge2_.partials_[n] += pow_n * log(y_dbl / sigma_dbl);
    }
    if (!is_constant_all<T_scale>::value) {
      ops_partials.edge3_.partials_[n] -= pow_n * alpha_dbl / sigma_dbl;
    }
  }

  if (!is_constant_all<T_y>::value) {
    for (size_t n = 0; n < stan::math::size(y); ++n) {
      ops_partials.edge1_.partials_[n] *= cdf;
    }
  }
  if (!is_constant_all<T_shape>::value) {
    for (size_t n = 0; n < stan::math::size(alpha); ++n) {
      ops_partials.edge2_.partials_[n] *= cdf;
    }
  }
  if (!is_constant_all<T_scale>::value) {
    for (size_t n = 0; n < stan::math::size(sigma); ++n) {
      ops_partials.edge3_.partials_[n] *= cdf;
    }
  }
  return ops_partials.build(cdf);
}

}  // namespace math
}  // namespace stan
#endif