#include "framework/builder/framework_builder.hpp"

#include <deal.II/base/conditional_ostream.h>
#include <deal.II/base/mpi.h>
#include <sstream>
#include <fstream>
#include <quadrature/calculators/angular_flux_integrator.hpp>
#include <quadrature/calculators/quadrature_calculators_factories.hpp>
#include <calculator/drift_diffusion/drift_diffusion_vector_calculator.hpp>
#include <calculator/drift_diffusion/factory.hpp>
#include <formulation/scalar/scalar_formulation_factory.hpp>
#include <formulation/updater/formulation_updater_factories.hpp>
#include <iteration/subroutine/get_scalar_flux_from_framework.hpp>

// Builders & factories
#include "solver/builder/solver_builder.hpp"

// Convergence classes
#include "convergence/iteration_completion_checker.hpp"
#include "convergence/moments/convergence_checker_l1_norm.hpp"
#include "convergence/moments/convergence_checker_l_infinity_norm.hpp"
#include "convergence/moments/multi_moment_checker_max.hpp"
#include "convergence/parameters/single_parameter_checker.hpp"

// Domain classes
#include "domain/domain.hpp"
#include "domain/finite_element/finite_element_gaussian.hpp"
#include "domain/mesh/mesh_cartesian.hpp"

// Formulation classes
#include "formulation/angular/self_adjoint_angular_flux.h"
#include "formulation/scalar/diffusion.hpp"
#include "formulation/scalar/drift_diffusion.hpp"
#include "formulation/stamper.hpp"
#include "formulation/updater/saaf_updater.h"
#include "formulation/updater/diffusion_updater.hpp"
#include "formulation/updater/drift_diffusion_updater.hpp"

// Framework class
#include "framework/framework.hpp"

// KEffective Updater Classes
#include "calculator/cell/total_aggregated_fission_source.hpp"
#include "calculator/cell/integrated_fission_source.hpp"
#include "eigenvalue/k_eigenvalue/calculator_via_fission_source.hpp"
#include "eigenvalue/k_eigenvalue/calculator_via_rayleigh_quotient.hpp"

// Material classes
#include "data/material/material_protobuf.hpp"

// Iteration classes
#include "iteration/initializer/initialize_fixed_terms_once.h"
#include "iteration/initializer/initialize_fixed_terms_reset_moments.hpp"
#include "iteration/group/group_solve_iteration.hpp"
#include "iteration/group/group_source_iteration.hpp"
#include "iteration/outer/outer_power_iteration.hpp"
#include "iteration/outer/outer_fixed_source_iteration.hpp"

// Quadrature classes & factories
#include "quadrature/quadrature_generator_i.h"
#include "quadrature/factory/quadrature_factories.h"
#include "quadrature/utility/quadrature_utilities.h"

// Results class
#include "results/output_dealii_vtu.h"

// System classes
#include "system/system.hpp"
#include "system/solution/mpi_group_angular_solution.h"
#include "system/solution/solution_types.h"

// Instrumentation
#include "instrumentation/builder/instrument_builder.hpp"

namespace bart::framework::builder {

namespace  {

using InstrumentBuilder = instrumentation::builder::InstrumentBuilder;
using InstrumentName = instrumentation::builder::InstrumentName;
using StringColorPair = std::pair<std::string, utility::Color>;

} // namespace

template<int dim>
FrameworkBuilder<dim>::FrameworkBuilder(std::unique_ptr<Validator> validator_ptr)
    : validator_ptr_(std::move(validator_ptr)) {}

// =============================================================================

template<int dim>
auto FrameworkBuilder<dim>::BuildAngularFluxIntegrator(const std::shared_ptr<QuadratureSet> quadrature_set_ptr)
-> std::unique_ptr<AngularFluxIntegrator> {
  return quadrature::calculators::AngularFluxIntegrator<dim>::Factory::get()
      .GetConstructor(quadrature::calculators::AngularFluxIntegratorName::kDefaultImplementation)(quadrature_set_ptr);
}

template<int dim>
auto FrameworkBuilder<dim>::BuildDiffusionFormulation(const std::shared_ptr<FiniteElement>& finite_element_ptr,
                                                      const std::shared_ptr<data::cross_sections::CrossSectionsI>& cross_sections_ptr,
                                                      const DiffusionFormulationImpl implementation)
-> std::unique_ptr<DiffusionFormulation> {
  ReportBuildingComponant("Diffusion formulation");
  std::unique_ptr<DiffusionFormulation> return_ptr = nullptr;

  if (implementation == DiffusionFormulationImpl::kDefault) {
    using ReturnType = formulation::scalar::Diffusion<dim>;
    return_ptr = std::move(std::make_unique<ReturnType>(finite_element_ptr, cross_sections_ptr));
  }
  ReportBuildSuccess(return_ptr->description());

  return return_ptr;
}

template<int dim>
auto FrameworkBuilder<dim>::BuildDriftDiffusionFormulation(
    const std::shared_ptr<AngularFluxIntegrator>& angular_flux_integrator_ptr,
    const std::shared_ptr<FiniteElement>& finite_element_ptr,
    const std::shared_ptr<data::cross_sections::CrossSectionsI>& cross_sections_ptr) -> std::unique_ptr<DriftDiffusionFormulation> {
  auto drift_diffusion_vector_calculator_ptr = Shared(calculator::drift_diffusion::DriftDiffusionVectorCalculatorIFactory<dim>::get()
      .GetConstructor(calculator::drift_diffusion::DriftDiffusionVectorCalculatorName::kDefaultImplementation)());
  return formulation::scalar::DriftDiffusion<dim>::Factory::get()
      .GetConstructor(formulation::scalar::DriftDiffusionFormulationName::kDefaultImplementation)(
          finite_element_ptr, cross_sections_ptr, drift_diffusion_vector_calculator_ptr, angular_flux_integrator_ptr);
}

template<int dim>
auto FrameworkBuilder<dim>::BuildDomain(FrameworkParameters::DomainSize domain_size,
                                        FrameworkParameters::NumberOfCells number_of_cells,
                                        const std::shared_ptr<FiniteElement>& finite_element_ptr,
                                        std::string material_mapping) -> std::unique_ptr<Domain> {
  std::unique_ptr<Domain> return_ptr = nullptr;
  try {
    ReportBuildingComponant("Mesh");
    auto mesh_ptr = std::make_unique<domain::mesh::MeshCartesian<dim>>(
        domain_size.get(), number_of_cells.get(), material_mapping);
    ReportBuildSuccess(mesh_ptr->description());

    ReportBuildingComponant("Domain");
    return_ptr = std::move(std::make_unique<domain::Domain<dim>>(std::move(mesh_ptr), finite_element_ptr));
    ReportBuildSuccess(return_ptr->description());
  } catch (...) {
    ReportBuildError();
    throw;
  }
  return return_ptr;
}

template<int dim>
auto FrameworkBuilder<dim>::BuildFiniteElement(problem::CellFiniteElementType finite_element_type,
                                               problem::DiscretizationType discretization_type,
                                               FrameworkParameters::PolynomialDegree polynomial_degree)
-> std::unique_ptr<FiniteElement> {
  ReportBuildingComponant("Cell finite element basis");
  std::unique_ptr<FiniteElement> return_ptr{ nullptr };

  try {
    AssertThrow(polynomial_degree.get() > 0, dealii::ExcMessage("Bad polynomial degree"))
    switch (finite_element_type) {
      case problem::CellFiniteElementType::kGaussian: {
        using ReturnType = domain::finite_element::FiniteElementGaussian<dim>;
        return_ptr = std::move(std::make_unique<ReturnType>(discretization_type, polynomial_degree.get()));
      }
    }
  } catch (...) {
    ReportBuildError();
    throw;
  }
  ReportBuildSuccess(return_ptr->description());
  return return_ptr;
}

template<int dim>
auto FrameworkBuilder<dim>::BuildUpdaterPointers(
    std::unique_ptr<DiffusionFormulation> diffusion_formulation_ptr,
    std::unique_ptr<DriftDiffusionFormulation> drift_diffusion_formulation_ptr,
    std::shared_ptr<Stamper> stamper_ptr,
    std::shared_ptr<AngularFluxIntegrator> angular_flux_integrator_ptr,
    std::shared_ptr<SphericalHarmonicMoments> higher_order_moments_ptr,
    AngularFluxStorage& angular_flux_storage,
    const std::map<problem::Boundary, bool>& reflective_boundaries) -> UpdaterPointers {
  ReportBuildingComponant("Building Drift-Diffusion Formulation updater");
  UpdaterPointers return_struct;

  std::unordered_set<problem::Boundary> reflective_boundary_set;

  for (const auto boundary_pair : reflective_boundaries) {
    if (boundary_pair.second)
      reflective_boundary_set.insert(boundary_pair.first);
  }

  using ReturnType = formulation::updater::DriftDiffusionUpdater<dim>;
  auto implementation_name{ formulation::updater::DriftDiffusionUpdaterName::kDefaultImplementation };
  auto drift_diffusion_updater_ptr = Shared(ReturnType::Factory::get().GetConstructor(implementation_name)(
      std::move(diffusion_formulation_ptr),
      std::move(drift_diffusion_formulation_ptr),
      stamper_ptr,
      angular_flux_integrator_ptr,
      higher_order_moments_ptr,
      angular_flux_storage,
      reflective_boundary_set));

  return_struct.fixed_updater_ptr = drift_diffusion_updater_ptr;
  return_struct.fission_source_updater_ptr = drift_diffusion_updater_ptr;
  return_struct.scattering_source_updater_ptr = drift_diffusion_updater_ptr;

  return return_struct;
}

template<int dim>
auto FrameworkBuilder<dim>::BuildUpdaterPointers(
    std::unique_ptr<DiffusionFormulation> formulation_ptr,
    std::unique_ptr<Stamper> stamper_ptr,
    const std::map<problem::Boundary, bool>& reflective_boundaries) -> UpdaterPointers {
  ReportBuildingComponant("Building Diffusion Formulation updater");
  UpdaterPointers return_struct;

  std::unordered_set<problem::Boundary> reflective_boundary_set;

  for (const auto boundary_pair : reflective_boundaries) {
    if (boundary_pair.second)
      reflective_boundary_set.insert(boundary_pair.first);
  }

  using ReturnType = formulation::updater::DiffusionUpdater<dim>;

  auto diffusion_updater_ptr = std::make_shared<ReturnType>(std::move(formulation_ptr),
                                                            std::move(stamper_ptr),
                                                            reflective_boundary_set);
  ReportBuildSuccess(diffusion_updater_ptr->description());
  return_struct.fixed_updater_ptr = diffusion_updater_ptr;
  return_struct.scattering_source_updater_ptr = diffusion_updater_ptr;
  return_struct.fission_source_updater_ptr = diffusion_updater_ptr;

  return return_struct;
}

template<int dim>
auto FrameworkBuilder<dim>::BuildUpdaterPointers(
    std::unique_ptr<SAAFFormulation> formulation_ptr,
    std::unique_ptr<Stamper> stamper_ptr,
    const std::shared_ptr<QuadratureSet>& quadrature_set_ptr) -> UpdaterPointers {
  ReportBuildingComponant("Building SAAF Formulation updater");
  UpdaterPointers return_struct;

  using ReturnType = formulation::updater::SAAFUpdater<dim>;
  auto saaf_updater_ptr = std::make_shared<ReturnType>(std::move(formulation_ptr),
                                                       std::move(stamper_ptr),
                                                       quadrature_set_ptr);
  ReportBuildSuccess(saaf_updater_ptr->description());
  return_struct.fixed_updater_ptr = saaf_updater_ptr;
  return_struct.scattering_source_updater_ptr = saaf_updater_ptr;
  return_struct.fission_source_updater_ptr = saaf_updater_ptr;

  return return_struct;
}

template<int dim>
auto FrameworkBuilder<dim>::BuildUpdaterPointers(
    std::unique_ptr<SAAFFormulation> formulation_ptr,
    std::unique_ptr<Stamper> stamper_ptr,
    const std::shared_ptr<QuadratureSet>& quadrature_set_ptr,
    const std::map<problem::Boundary, bool>& reflective_boundaries,
    const AngularFluxStorage& angular_flux_storage) -> UpdaterPointers {
  ReportBuildingComponant("Building SAAF Formulation updater (with boundary conditions update)");
  UpdaterPointers return_struct;

  // Transform map into set

  std::unordered_set<problem::Boundary> reflective_boundary_set;

  for (const auto boundary_pair : reflective_boundaries) {
    if (boundary_pair.second)
      reflective_boundary_set.insert(boundary_pair.first);
  }

  using ReturnType = formulation::updater::SAAFUpdater<dim>;
  auto saaf_updater_ptr = std::make_shared<ReturnType>(std::move(formulation_ptr),
                                                       std::move(stamper_ptr),
                                                       quadrature_set_ptr,
                                                       angular_flux_storage,
                                                       reflective_boundary_set);
  ReportBuildSuccess(saaf_updater_ptr->description());

  return_struct.fixed_updater_ptr = saaf_updater_ptr;
  return_struct.scattering_source_updater_ptr = saaf_updater_ptr;
  return_struct.fission_source_updater_ptr = saaf_updater_ptr;
  return_struct.boundary_conditions_updater_ptr = saaf_updater_ptr;

  return return_struct;
}

template <int dim>
auto FrameworkBuilder<dim>::BuildGroupSolveIteration(
    std::unique_ptr<SingleGroupSolver> single_group_solver_ptr,
    std::unique_ptr<MomentConvergenceChecker> moment_convergence_checker_ptr,
    std::unique_ptr<MomentCalculator> moment_calculator_ptr,
    const std::shared_ptr<GroupSolution>& group_solution_ptr,
    const UpdaterPointers& updater_ptrs,
    std::unique_ptr<MomentMapConvergenceChecker> moment_map_convergence_checker_ptr)
    -> std::unique_ptr<GroupSolveIteration> {
  std::unique_ptr<GroupSolveIteration> return_ptr = nullptr;

  ReportBuildingComponant("Iterative group solver");

  if (updater_ptrs.boundary_conditions_updater_ptr == nullptr) {
    return_ptr = std::move(
        std::make_unique<iteration::group::GroupSourceIteration<dim>>(
            std::move(single_group_solver_ptr),
            std::move(moment_convergence_checker_ptr),
            std::move(moment_calculator_ptr),
            group_solution_ptr,
            updater_ptrs.scattering_source_updater_ptr,
            std::move(moment_map_convergence_checker_ptr))
    );
  } else {
    return_ptr = std::move(
        std::make_unique<iteration::group::GroupSourceIteration<dim>>(
            std::move(single_group_solver_ptr),
            std::move(moment_convergence_checker_ptr),
            std::move(moment_calculator_ptr),
            group_solution_ptr,
            updater_ptrs.scattering_source_updater_ptr,
            updater_ptrs.boundary_conditions_updater_ptr,
            std::move(moment_map_convergence_checker_ptr))
    );
  }

  using ConvergenceStatusPort = iteration::group::data_ports::ConvergenceStatusPort;
  using StatusPort = iteration::group::data_ports::StatusPort;

  instrumentation::GetPort<ConvergenceStatusPort>(*return_ptr)
      .AddInstrument(convergence_status_instrument_ptr_);
  instrumentation::GetPort<StatusPort>(*return_ptr)
      .AddInstrument(status_instrument_ptr_);

  validator_ptr_->AddPart(FrameworkPart::ScatteringSourceUpdate);
  ReportBuildSuccess(return_ptr->description());
  return return_ptr;
}

template<int dim>
auto FrameworkBuilder<dim>::BuildGroupSolution(const int n_angles) -> std::unique_ptr<GroupSolution> {
  std::unique_ptr<GroupSolution> return_ptr = nullptr;
  ReportBuildingComponant("Group solution");

  return_ptr = std::move(std::make_unique<system::solution::MPIGroupAngularSolution>(n_angles));
  ReportBuildSuccess(return_ptr->description());
  return return_ptr;
}

template<int dim>
auto FrameworkBuilder<dim>::BuildInitializer(const std::shared_ptr<FixedTermUpdater>& updater_ptr,
                                             const int total_groups,
                                             const int total_angles,
                                             const InitializerName implementation) -> std::unique_ptr<Initializer> {
  ReportBuildingComponant("Initializer");

  std::unique_ptr<Initializer> return_ptr = nullptr;

  if (implementation == InitializerName::kInitializeFixedTermsOnce) {
    using InitializeOnceType = iteration::initializer::InitializeFixedTermsOnce;
    return_ptr = std::move(std::make_unique<InitializeOnceType>(updater_ptr, total_groups, total_angles));
  } else if (implementation == InitializerName::kInitializeFixedTermsAndResetMoments) {
    using InitializeAndResetMoments = iteration::initializer::InitializeFixedTermsResetMoments;
    return_ptr = std::move(std::make_unique<InitializeAndResetMoments>(updater_ptr, total_groups, total_angles));
  }

  ReportBuildSuccess(return_ptr->description());
  return return_ptr;
}

template<int dim>
auto FrameworkBuilder<dim>::BuildInitializer(const std::shared_ptr<FixedTermUpdater>& updater_ptr,
                                             const int total_groups,
                                             const int total_angles) -> std::unique_ptr<Initializer> {
  return BuildInitializer(updater_ptr, total_groups, total_angles, InitializerName::kInitializeFixedTermsOnce);
}

template<int dim>
auto FrameworkBuilder<dim>::BuildKEffectiveUpdater() -> std::unique_ptr<KEffectiveUpdater> {
  using ReturnType = eigenvalue::k_eigenvalue::CalculatorViaRayleighQuotient;
  ReportBuildingComponant("K_Effective updater");
  std::unique_ptr<KEffectiveUpdater> return_ptr{ nullptr };
  return_ptr = std::move(std::make_unique<ReturnType>());
  ReportBuildSuccess(return_ptr->description());
  return return_ptr;
}

template<int dim>
auto FrameworkBuilder<dim>::BuildKEffectiveUpdater(
    const std::shared_ptr<FiniteElement>& finite_element_ptr,
    const std::shared_ptr<CrossSections>& cross_sections_ptr,
    const std::shared_ptr<Domain>& domain_ptr)
-> std::unique_ptr<KEffectiveUpdater> {
  using AggregatedFissionSource = calculator::cell::TotalAggregatedFissionSource<dim>;
  using IntegratedFissionSource = calculator::cell::IntegratedFissionSource<dim>;
  using ReturnType = eigenvalue::k_eigenvalue::CalculatorViaFissionSource;

  ReportBuildingComponant("K_Effective updater");
  std::unique_ptr<KEffectiveUpdater> return_ptr = nullptr;

  return_ptr = std::move(
      std::make_unique<ReturnType>(
          std::make_unique<AggregatedFissionSource>(
              std::make_unique<IntegratedFissionSource>(finite_element_ptr,
                                                        cross_sections_ptr),
              domain_ptr),
          2.0,
          10));

  ReportBuildSuccess(return_ptr->description());
  return return_ptr;
}

template<int dim>
auto FrameworkBuilder<dim>::BuildMomentCalculator(MomentCalculatorImpl implementation)
-> std::unique_ptr<MomentCalculator> {
  return BuildMomentCalculator(nullptr, implementation);
}

template<int dim>
auto FrameworkBuilder<dim>::BuildMomentCalculator(std::shared_ptr<QuadratureSet> quadrature_set_ptr,
                                                  FrameworkBuilder::MomentCalculatorImpl implementation)
-> std::unique_ptr<MomentCalculator> {
  ReportBuildingComponant("Moment calculator");
  std::unique_ptr<MomentCalculator> return_ptr = nullptr;

  try {
    return_ptr = std::move(quadrature::factory::MakeMomentCalculator<dim>(implementation, quadrature_set_ptr));

    if (implementation == MomentCalculatorImpl::kScalarMoment) {
      ReportBuildSuccess("(default) calculator for scalar solve");
    } else if (implementation == MomentCalculatorImpl::kZerothMomentOnly) {
      ReportBuildSuccess("(default) calculator for 0th moment only");
    } else {
      AssertThrow(false,
                  dealii::ExcMessage("Unsupported implementation of moment calculator specified in call to "
                                     "BuildMomentCalculator"))
    }
  } catch (...) {
    ReportBuildError();
    throw;
  }

  return return_ptr;
}

template<int dim>
auto FrameworkBuilder<dim>::BuildMomentConvergenceChecker(
    double max_delta,
    int max_iterations) -> std::unique_ptr<MomentConvergenceChecker>{
  //TODO(Josh): Add option for using other than L1Norm
  ReportBuildingComponant("Moment convergence checker");

  using CheckerType = convergence::moments::ConvergenceCheckerLInfinityNorm;
  using FinalCheckerType = convergence::IterationCompletionChecker<system::moments::MomentVector>;

  auto return_ptr = std::make_unique<FinalCheckerType>(std::make_unique<CheckerType>(max_delta));
  return_ptr->SetMaxIterations(max_iterations);
  return return_ptr;
}

template <int dim>
auto FrameworkBuilder<dim>::BuildMomentMapConvergenceChecker(
    double max_delta, int max_iterations)
-> std::unique_ptr<MomentMapConvergenceChecker> {
  ReportBuildingComponant("Moment map convergence checker");

  using SingleCheckerType = convergence::moments::ConvergenceCheckerLInfinityNorm;
  using CheckerType = convergence::moments::MultiMomentCheckerMax;
  using FinalCheckerType = convergence::IterationCompletionChecker<system::moments::MomentsMap>;

  auto return_ptr = std::make_unique<FinalCheckerType>(
      std::make_unique<CheckerType>(std::make_unique<SingleCheckerType>(max_delta)));
  return_ptr->SetMaxIterations(max_iterations);
  return return_ptr;
}
template <int dim>
auto FrameworkBuilder<dim>::BuildOuterIteration(
    std::unique_ptr<GroupSolveIteration> group_iteration_ptr,
    std::unique_ptr<ParameterConvergenceChecker> convergence_checker_ptr,
    const std::string&)
    -> std::unique_ptr<OuterIteration> {
  ReportBuildingComponant("Outer iteration");
  std::unique_ptr<OuterIteration> return_ptr = nullptr;
  using ReturnType = iteration::outer::OuterFixedSourceIteration;

  return_ptr = std::move(std::make_unique<ReturnType>(
      std::move(group_iteration_ptr),
      std::move(convergence_checker_ptr)));

  using ConvergenceDataPort = iteration::outer::data_names::ConvergenceStatusPort;
  using StatusPort =  iteration::outer::data_names::StatusPort;

  instrumentation::GetPort<ConvergenceDataPort>(*return_ptr)
      .AddInstrument(convergence_status_instrument_ptr_);

  instrumentation::GetPort<StatusPort>(*return_ptr)
      .AddInstrument(status_instrument_ptr_);

  ReportBuildSuccess(return_ptr->description());

  return return_ptr;
}

template<int dim>
auto FrameworkBuilder<dim>::BuildOuterIteration(
    std::unique_ptr<GroupSolveIteration> group_solve_iteration_ptr,
    std::unique_ptr<ParameterConvergenceChecker> parameter_convergence_checker_ptr,
    std::unique_ptr<KEffectiveUpdater> k_effective_updater_ptr,
    const std::shared_ptr<FissionSourceUpdater>& fission_source_updater_ptr,
    const std::string& output_filename_base)
-> std::unique_ptr<OuterIteration> {
  std::unique_ptr<OuterIteration> return_ptr = nullptr;
  ReportBuildingComponant("Outer Iteration");

  using DefaultOuterPowerIteration = iteration::outer::OuterPowerIteration;

  return_ptr = std::move(
      std::make_unique<DefaultOuterPowerIteration>(
          std::move(group_solve_iteration_ptr),
          std::move(parameter_convergence_checker_ptr),
          std::move(k_effective_updater_ptr),
          fission_source_updater_ptr));

  using ConvergenceDataPort = iteration::outer::data_names::ConvergenceStatusPort;
  using StatusPort =  iteration::outer::data_names::StatusPort;
  using IterationErrorPort = iteration::outer::data_names::IterationErrorPort;

  using InstrumentBuilder = instrumentation::builder::InstrumentBuilder;
  instrumentation::GetPort<ConvergenceDataPort>(*return_ptr)
      .AddInstrument(convergence_status_instrument_ptr_);
  instrumentation::GetPort<StatusPort>(*return_ptr)
      .AddInstrument(status_instrument_ptr_);
  instrumentation::GetPort<IterationErrorPort>(*return_ptr)
      .AddInstrument(Shared(InstrumentBuilder::BuildInstrument<std::pair<int,double>>(
          instrumentation::builder::InstrumentName::kIntDoublePairToFile,
          output_filename_base + "_iteration_error.csv")));

  validator_ptr_->AddPart(FrameworkPart::FissionSourceUpdate);
  ReportBuildSuccess(return_ptr->description());
  return return_ptr;
}

template<int dim>
auto FrameworkBuilder<dim>::BuildParameterConvergenceChecker(
    double max_delta, int max_iterations)
-> std::unique_ptr<ParameterConvergenceChecker>{
  ReportBuildingComponant("Parameter (double) convergence checker");
  using CheckerType = convergence::parameters::SingleParameterChecker;
  using FinalCheckerType = convergence::IterationCompletionChecker<double>;

  auto return_ptr = std::make_unique<FinalCheckerType>(std::make_unique<CheckerType>(max_delta));
  return_ptr->SetMaxIterations(max_iterations);

  return return_ptr;
}

template <int dim>
auto FrameworkBuilder<dim>::BuildQuadratureSet(
    const problem::AngularQuadType quadrature_type,
    const FrameworkParameters::AngularQuadratureOrder order) -> std::shared_ptr<QuadratureSet> {
  ReportBuildingComponant("quadrature set");
  using QuadratureGenerator = quadrature::QuadratureGeneratorI<dim>;

  std::shared_ptr<QuadratureSet> return_ptr{ nullptr };
  std::shared_ptr<QuadratureGenerator> quadrature_generator_ptr{ nullptr };

  try {

    switch (quadrature_type) {
      case problem::AngularQuadType::kLevelSymmetricGaussian: {
        AssertThrow(dim == 3, dealii::ExcMessage("Error in BuildQuadratureSet LSGC only available for 3D"))
        quadrature_generator_ptr = quadrature::factory::MakeAngularQuadratureGeneratorPtr<dim>(
            order, quadrature::AngularQuadratureSetType::kLevelSymmetricGaussian);
        break;
      }
      case problem::AngularQuadType::kGaussLegendre: {
        AssertThrow(dim == 1, dealii::ExcMessage("Error in BuildQuadratureSet GaussLegendre only available for 1D"))
        quadrature_generator_ptr =
            quadrature::factory::MakeAngularQuadratureGeneratorPtr<dim>(
                order, quadrature::AngularQuadratureSetType::kGaussLegendre);
        break;
      }
      default: {
        AssertThrow(false, dealii::ExcMessage("No supported quadratures for this dimension and transport model"))
        break;
      }
    }
    ReportBuildSuccess(quadrature_generator_ptr->description());

    return_ptr = quadrature::factory::MakeQuadratureSetPtr<dim>();

    auto quadrature_points = quadrature::utility::GenerateAllPositiveX<dim>(quadrature_generator_ptr->GenerateSet());

    quadrature::factory::FillQuadratureSet<dim>(return_ptr.get(), quadrature_points);

    return return_ptr;
  } catch (...) {
    ReportBuildError();
    throw;
  }
}

template <int dim>
auto FrameworkBuilder<dim>::BuildSAAFFormulation(
    const std::shared_ptr<FiniteElement>& finite_element_ptr,
    const std::shared_ptr<data::cross_sections::CrossSectionsI>& cross_sections_ptr,
    const std::shared_ptr<QuadratureSet>& quadrature_set_ptr,
    const formulation::SAAFFormulationImpl implementation) -> std::unique_ptr<SAAFFormulation> {
  ReportBuildingComponant("Building SAAF Formulation");
  std::unique_ptr<SAAFFormulation> return_ptr;

  if (implementation == formulation::SAAFFormulationImpl::kDefault) {
    using ReturnType = formulation::angular::SelfAdjointAngularFlux<dim>;
    return_ptr = std::move(std::make_unique<ReturnType>(finite_element_ptr, cross_sections_ptr, quadrature_set_ptr));
  }

  return return_ptr;
}

template<int dim>
auto FrameworkBuilder<dim>::BuildSingleGroupSolver(const int max_iterations, const double convergence_tolerance)
-> std::unique_ptr<SingleGroupSolver> {
  using SolverName = solver::builder::SolverName;
  using SolverBuilder = solver::builder::SolverBuilder;

  ReportBuildingComponant("Single group solver");
  std::unique_ptr<SingleGroupSolver> return_ptr = nullptr;

  return_ptr = std::move(SolverBuilder::BuildSolver(SolverName::kDefaultGMRESGroupSolver, max_iterations,
                                                    convergence_tolerance));

  ReportBuildSuccess("Default implementation with GMRES");

  return return_ptr;
}

template<int dim>
auto FrameworkBuilder<dim>::BuildSystem(
    const int total_groups,
    const int total_angles,
    const Domain& domain,
    const std::size_t solution_size,
    bool is_eigenvalue_problem,
    bool need_rhs_boundary_condition) -> std::unique_ptr<System> {
  std::unique_ptr<System> return_ptr;

  ReportBuildingComponant("system");
  try {
    return_ptr = std::move(std::make_unique<System>());
    system_helper_.InitializeSystem(*return_ptr, total_groups, total_angles,
                             is_eigenvalue_problem, need_rhs_boundary_condition);
    system_helper_.SetUpSystemTerms(*return_ptr, domain);
    system_helper_.SetUpSystemMoments(*return_ptr, solution_size);
    ReportBuildSuccess("system");
  } catch (...) {
    ReportBuildError("system initialization error.");
    throw;
  }

  return return_ptr;
}

template<int dim>
auto FrameworkBuilder<dim>::BuildStamper(const std::shared_ptr<Domain>& domain_ptr) -> std::unique_ptr<Stamper> {
  ReportBuildingComponant("Stamper");
  std::unique_ptr<Stamper> return_ptr = nullptr;

  return_ptr = std::move(std::make_unique<formulation::Stamper<dim>>(domain_ptr));
  ReportBuildSuccess(return_ptr->description());
  return return_ptr;
}

template<int dim>
auto FrameworkBuilder<dim>::BuildSubroutine(std::unique_ptr<FrameworkI> framework_ptr,
                                            const SubroutineName subroutine_name) -> std::unique_ptr<Subroutine> {
  std::unique_ptr<Subroutine> return_ptr{ nullptr };
  if (subroutine_name == SubroutineName::kGetScalarFluxFromFramework) {
    using ReturnType = iteration::subroutine::GetScalarFluxFromFramework;
    return_ptr = std::move(std::make_unique<ReturnType>(std::move(framework_ptr)));
  }
  return return_ptr;
}

template<int dim>
void FrameworkBuilder<dim>::Validate() const {
  validator_ptr_->ReportValidation();
}

template class FrameworkBuilder<1>;
template class FrameworkBuilder<2>;
template class FrameworkBuilder<3>;

} // namespace bart::framework::builder


//
// Created by yale on 8/14/18.
//

#include <random>
#include <cmath>
#include <boost/algorithm/hex.hpp>
#include <eosio/chain/forest_bank.hpp>
#include <celesos/miner_plugin/miner.hpp>

using namespace std;
using namespace eosio;
using namespace celesos;

using celesos::miner::worker;
using celesos::miner::worker_ctx;

using boost::multiprecision::uint256_t;
using boost::signals2::connection;

celesos::miner::miner::miner(const fc::logger &logger,
                             boost::asio::io_service &main_io_service,
                             unsigned int worker_count,
                             uint32_t sleep_interval_sec,
                             float sleep_probability) :
        _logger{logger},
        _alive_worker_ptrs{0, vector<shared_ptr<worker>>::allocator_type()},
        _signal_ptr{make_shared<celesos::miner::mine_signal_type>()},
        _main_io_service_ref{main_io_service},
        _io_thread{&celesos::miner::miner::run, this},
        _state{state::initialized},
        _worker_count{worker_count},
        _failure_retry_interval_us{fc::milliseconds(5000)},
        _sleep_interval_sec{sleep_interval_sec},
        _sleep_probability{sleep_probability},
        _last_job_id{0} {
}

celesos::miner::miner::~miner() {
    fc_dlog(_logger, "begin miner::~miner()");
    if (this->_signal_ptr) {
        this->_signal_ptr->disconnect_all_slots();
        this->_signal_ptr.reset();
    }
    this->stop(true);
    if (this->_sub_io_service_ptr) {
        this->_sub_io_service_ptr->stop();
        this->_sub_io_service_ptr.reset();
    }
    fc_dlog(_logger, "alive_worker_count: ${1}", ("1", this->_alive_worker_ptrs.size()));
    if (this->_io_thread.joinable()) {
        this->_io_thread.detach();
    }
    fc_dlog(_logger, "end miner::~miner()");
}

void celesos::miner::miner::start(const chain::account_name &relative_account, chain::controller &cc) {
    fc_dlog(_logger, "attempt miner::start()");
    if (this->_state == state::started) {
        return;
    }
    fc_dlog(_logger, "begin miner::start()");
    this->_state = state::started;

    auto slot = [this, &relative_account, &cc](const chain::block_state_ptr block_ptr) {
        if (this->_last_failure_time_us) {
            auto &&passed_time_us = fc::time_point::now().time_since_epoch() - this->_last_failure_time_us.get();
            if (passed_time_us < this->_failure_retry_interval_us) {
                return;
            }
        }

        if (this->_last_forest_info_opt && (
                this->_last_forest_info_opt->next_block_num == 0 ||
                block_ptr->block_num < this->_last_forest_info_opt->next_block_num
        )) {
            return;
        }

        bool exception_occurred = true;
        try {

            auto &bank = *forest::forest_bank::getInstance(cc);

            forest::forest_struct new_forest_info{};
            if (!bank.get_forest(new_forest_info, relative_account)) {
                FC_THROW_EXCEPTION(fc::unhandled_exception,
                                   "Fail to get forest with account: ${account}",
                                   ("account", relative_account));
            }

            bool no_old_forest = !this->_last_forest_info_opt;

            bool is_forest_updated = false;
            if (!no_old_forest) {
                auto lhs = *this->_last_forest_info_opt;
                auto &rhs = new_forest_info;
                if (lhs.seed != rhs.seed || lhs.forest != rhs.forest || lhs.target != rhs.target) {
                    is_forest_updated = true;
                }
            }

            if (no_old_forest || is_forest_updated) {
                fc_ilog(_logger, "\n\tsuccess to get latest forest_info with "
                                 "\n\t\tseed: ${seed} "
                                 "\n\t\tforest: ${forest} "
                                 "\n\t\tblock_num: ${block_num}"
                                 "\n\t\tnext_block_num: ${next_block_num}"
                                 "\n\t\ttarget: ${target}",
                        ("seed", new_forest_info.seed.str())
                                ("forest", new_forest_info.forest.str())
                                ("block_num", new_forest_info.block_number)
                                ("next_block_num", new_forest_info.next_block_num)
                                ("target", new_forest_info.target.str(0, std::ios_base::hex)));

                this->_last_job_id += 1;
                auto old_forest_info = no_old_forest ? new_forest_info : *this->_last_forest_info_opt;

                auto handler = std::bind(&miner::on_forest_updated, this,
                                         old_forest_info, new_forest_info,
                                         relative_account, no_old_forest);
                this->_sub_io_service_ptr->post(handler);
            }

            this->_last_forest_info_opt = new_forest_info;

            exception_occurred = false;
        } catch (fc::exception &er) {
            wlog("${details}", ("details", er.to_detail_string()));
        } catch (const std::exception &e) {
            fc::exception fce{FC_LOG_MESSAGE(warn, "rethrow ${what}: ", ("what", e.what())),
                              fc::std_exception_code, BOOST_CORE_TYPEID(e).name(), e.what()};
            wlog("${details}", ("details", fce.to_detail_string()));
        } catch (...) {
            fc::unhandled_exception e{FC_LOG_MESSAGE(warn, "rethrow"), std::current_exception()};
            wlog("${details}", ("details", e.to_detail_string()));
        }

        if (exception_occurred) {
            this->_last_failure_time_us = fc::time_point::now().time_since_epoch();
            fc_elog(_logger, "Fail to handle \"on_forest_update\"");
            return;
        }

        // clear last_failure_time_us for performance
        this->_last_failure_time_us.reset();
    };
    auto a_connection = cc.accepted_block_header.connect(slot);
    this->_connections.push_back(std::move(a_connection));
    fc_dlog(_logger, "end miner::start()");
}

void celesos::miner::miner::stop_workers(bool wait) {
    fc_dlog(_logger, "begin miner::stop_workers(wait = ${wait})", ("wait", wait));
    for (auto &x : this->_alive_worker_ptrs) {
        if (x) {
            x->stop(wait);
            x.reset();
        }
    }
    this->_alive_worker_ptrs.clear();
    fc_dlog(_logger, "end miner::stop_workers(wait = ${wait})", ("wait", wait));
}

void celesos::miner::miner::stop(bool wait) {
    fc_dlog(_logger, "begin miner::stop(wait = ${wait})", ("wait", wait));
    if (this->_state == state::stopped) {
        fc_dlog(_logger, "connection_count: ${1} worker_count: ${2}",
                ("1", this->_connections.size())("2", this->_alive_worker_ptrs.size()));
        fc_dlog(_logger, "end miner::stop(wait = ${wait})", ("wait", wait));
        return;
    }
    this->_state = state::stopped;

    for (auto &x : this->_connections) {
        x.disconnect();
    }
    this->_connections.clear();

    this->stop_workers(wait);

    fc_dlog(_logger, "end miner::stop(wait = ${wait})", ("wait", wait));
}

connection celesos::miner::miner::connect(const celesos::miner::mine_slot_type &slot) {
    return _signal_ptr->connect(slot);
}

void celesos::miner::miner::on_forest_updated(const forest::forest_struct &old_forest_info,
                                              const forest::forest_struct &new_forest_info,
                                              const chain::account_name &relative_account, bool force) {
    fc_ilog(_logger, "on forest updated");

    auto current_job_id = this->_last_job_id;
//    const auto target_ptr = make_shared<uint256_t>(
//            "0x0000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF");
    const auto target_ptr = make_shared<uint256_t>(new_forest_info.target);

    const auto seed_ptr = make_shared<string>(new_forest_info.seed.str());
    const auto forest_ptr = make_shared<string>(new_forest_info.forest.str());

    auto is_miner_quit_func = [this, current_job_id]() {
        auto &logger = this->_logger;
        if (this->_state == state::stopped) {
            fc_ilog(logger, "miner has been stop, quit");
            return true;
        } else if (current_job_id != this->_last_job_id) {
            fc_ilog(logger, "forest has changed, quit");
            return true;
        }
        return false;
    };

    // prepare cache and dataset_ptr
    const static auto is_cache_changed_func = [](const forest::forest_struct &old_forest,
                                                 const forest::forest_struct &new_forest,
                                                 const boost::optional<uint32_t> old_cache_count_opt,
                                                 uint32_t new_cache_count) {
        return !old_cache_count_opt ||
               old_forest.forest != new_forest.forest ||
               *old_cache_count_opt != new_cache_count;
    };
//    const uint32_t new_cache_count{512};

    if (is_miner_quit_func()) {
        return;
    }

    const auto new_cache_count = forest::cache_count();
    const auto is_cache_changed = is_cache_changed_func(old_forest_info, new_forest_info,
                                                        this->_target_cache_count_opt, new_cache_count);

    shared_ptr<vector<ethash::node>> cache_ptr{};
    if (force || is_cache_changed) {
        fc_ilog(_logger, "begin prepare cache with count: ${1}", ("1", new_cache_count));
        cache_ptr = make_shared<vector<ethash::node>>(new_cache_count, vector<ethash::node>::allocator_type());
        ethash::calc_cache(*cache_ptr, new_cache_count, *seed_ptr);
        fc_ilog(_logger, "end prepare cache with count: ${1}", ("1", new_cache_count));
    } else {
        fc_dlog(_logger, "use cache generated");
        cache_ptr = *this->_target_cache_ptr_opt;
    }

    const static auto is_dataset_changed_func = [](bool is_cache_changed,
                                                   const boost::optional<uint32_t> &old_dataset_count_opt,
                                                   uint32_t new_dataset_count) {
        return is_cache_changed ||
               !old_dataset_count_opt ||
               old_dataset_count_opt != new_dataset_count;
    };

    if (is_miner_quit_func()) {
        return;
    }

//    const uint32_t new_dataset_count{512 * 16};
    const auto new_dataset_count = forest::dataset_count();

    const auto is_dataset_changed = is_dataset_changed_func(is_cache_changed,
                                                            this->_target_dataset_count_opt, new_dataset_count);
    shared_ptr<vector<ethash::node>> dataset_ptr{};
    if (force || is_dataset_changed) {
        fc_ilog(_logger, "begin prepare dataset with count: ${1}", ("1", new_dataset_count));
        dataset_ptr = make_shared<vector<ethash::node>>(new_dataset_count,
                                                        vector<ethash::node>::allocator_type());

        auto &dataset = *dataset_ptr;
        auto &cache = *cache_ptr;
        dataset.resize(new_dataset_count);
        for (uint32_t i = 0; i < new_dataset_count; ++i) {
            if (i % 1000 == 0 && is_miner_quit_func()) {
                return;
            }
            dataset[i] = calc_dataset_item(cache, i);
        }

//        ethash::calc_dataset(*dataset_ptr, new_dataset_count, *cache_ptr);
        fc_ilog(_logger, "end prepare dataset with count: ${1}", ("1", new_dataset_count));
    } else {
        fc_dlog(_logger, "use dataset generated");
        dataset_ptr = *this->_target_dataset_ptr_opt;
    }

    const static auto is_work_changed_func = [](bool is_dataset_changed,
                                                const forest::forest_struct &old_forest,
                                                const forest::forest_struct &new_forest) {
        return is_dataset_changed || old_forest.forest != new_forest.forest;
    };

    if(is_miner_quit_func()) {
        return;
    }

    const auto is_work_changed = is_work_changed_func(is_dataset_changed, old_forest_info, new_forest_info);
    if (!force && !is_work_changed) {
        fc_dlog(_logger, "work not changed,no need to restart workers");
    } else {
        fc_dlog(_logger, "restart workers to logging");
        this->stop_workers(true);

        auto retry_count_ptr = make_shared<uint256_t>(-1);
        *retry_count_ptr /= _worker_count;

        uint256_t nonce_init{0};
        gen_random_uint256(nonce_init);

        this->_alive_worker_ptrs.resize(_worker_count);
        for (int i = 0; i < _worker_count; ++i) {
            auto nonce_start_ptr = make_shared<uint256_t>(nonce_init + (*retry_count_ptr) * i);
            worker_ctx ctx{
                    .logger = _logger,
                    .dataset_ptr = dataset_ptr,
                    .seed_ptr = seed_ptr,
                    .forest_ptr = forest_ptr,
                    .nonce_start_ptr = std::move(nonce_start_ptr),
                    .retry_count_ptr = retry_count_ptr,
                    .target_ptr = target_ptr,
                    .block_num = new_forest_info.block_number,
                    .io_service_ref = this->_main_io_service_ref,
                    .signal_ptr = this->_signal_ptr,
                    .sleep_interval_sec = this->_sleep_interval_sec,
                    .sleep_probability = this->_sleep_probability,
            };
            this->_alive_worker_ptrs[i] = make_shared<worker>(std::move(ctx));
        }

        if(is_miner_quit_func()) {
            return;
        }

        for (auto &x : this->_alive_worker_ptrs) {
            x->start();
        }
        fc_dlog(_logger, "start worker with count: ${1}", ("1", this->_alive_worker_ptrs.size()));
    }

    // update target fields
    this->_target_cache_ptr_opt = cache_ptr;
    this->_target_dataset_ptr_opt = dataset_ptr;
    this->_target_cache_count_opt = new_cache_count;
    this->_target_dataset_count_opt = new_dataset_count;

    fc_dlog(_logger, "end on_forested_updated()");
}

void celesos::miner::miner::run() {
    this->_sub_io_service_ptr = make_shared<boost::asio::io_service>();

    auto sub_io_service_ptr = this->_sub_io_service_ptr;
    boost::asio::io_service::work a_work{std::ref(*sub_io_service_ptr)};
    sub_io_service_ptr->run();
}

void celesos::miner::miner::gen_random_uint256(uint256_t &dst) {
    random_device rd{};
    mt19937_64 gen{rd()};
    uniform_int_distribution<uint64_t> dis{};
    dst = 0;
    for (int i = 0; i < 4; ++i) {
        dst <<= 64;
        dst |= dis(gen);
    }
}


/**************************************************************
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 *
 *************************************************************/

// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_cppcanvas.hxx"

#include <rtl/logfile.hxx>

#include <com/sun/star/rendering/XCanvas.hpp>
#include <com/sun/star/rendering/TexturingMode.hpp>

#include <tools/gen.hxx>
#include <vcl/canvastools.hxx>

#include <basegfx/range/b2drectangle.hxx>
#include <basegfx/tools/canvastools.hxx>
#include <basegfx/polygon/b2dpolypolygon.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <canvas/canvastools.hxx>

#include <boost/utility.hpp>

#include "cachedprimitivebase.hxx"
#include "polypolyaction.hxx"
#include "outdevstate.hxx"
#include "mtftools.hxx"

using namespace ::com::sun::star;

namespace cppcanvas
{
    namespace internal
    {
        namespace
        {
            class PolyPolyAction : public CachedPrimitiveBase
            {
            public:
                PolyPolyAction( const ::basegfx::B2DPolyPolygon&,
                                const CanvasSharedPtr&,
                                const OutDevState&,
                                bool bFill,
                                bool bStroke );
                PolyPolyAction( const ::basegfx::B2DPolyPolygon&,
                                const CanvasSharedPtr&,
                                const OutDevState&,
                                bool bFill,
                                bool bStroke,
                                int nTransparency );

                virtual bool render( const ::basegfx::B2DHomMatrix& rTransformation,
                                     const Subset&					rSubset ) const;

                virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const;
                virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix&	rTransformation,
                                                       const Subset&					rSubset ) const;

                virtual sal_Int32 getActionCount() const;

            private:
                using Action::render;
                virtual bool render( uno::Reference< rendering::XCachedPrimitive >& rCachedPrimitive,
                                     const ::basegfx::B2DHomMatrix&                 rTransformation ) const;

                const uno::Reference< rendering::XPolyPolygon2D > 	mxPolyPoly;
                const ::basegfx::B2DRange							maBounds;
                const CanvasSharedPtr								mpCanvas;

            	// stroke color is now implicit: the maState.DeviceColor member
                rendering::RenderState								maState;

                uno::Sequence< double >								maFillColor;
            };

            PolyPolyAction::PolyPolyAction( const ::basegfx::B2DPolyPolygon&	rPolyPoly,
                                            const CanvasSharedPtr&              rCanvas,
                                            const OutDevState&                  rState,
                                            bool                                bFill,
                                            bool                                bStroke ) :
                CachedPrimitiveBase( rCanvas, false ),
                mxPolyPoly( ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( rCanvas->getUNOCanvas()->getDevice(), rPolyPoly) ),
                maBounds( ::basegfx::tools::getRange(rPolyPoly) ),
                mpCanvas( rCanvas ),
                maState(),
                maFillColor()
            {
                tools::initRenderState(maState,rState);

                if( bFill )
                    maFillColor = rState.fillColor;

                if( bStroke )
                    maState.DeviceColor = rState.lineColor;
            }

            PolyPolyAction::PolyPolyAction( const ::basegfx::B2DPolyPolygon&	rPolyPoly,
                                            const CanvasSharedPtr&              rCanvas,
                                            const OutDevState&                  rState,
                                            bool                                bFill,
                                            bool                                bStroke,
                                            int                                 nTransparency ) :
                CachedPrimitiveBase( rCanvas, false ),
                mxPolyPoly( ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( rCanvas->getUNOCanvas()->getDevice(), rPolyPoly) ),
                maBounds( ::basegfx::tools::getRange(rPolyPoly) ),
                mpCanvas( rCanvas ),
                maState(),
                maFillColor()
            {
                tools::initRenderState(maState,rState);

                if( bFill )
                {
                    maFillColor = rState.fillColor;

                    if( maFillColor.getLength() < 4 )
                        maFillColor.realloc( 4 );

                    // TODO(F1): Color management
                    // adapt fill color transparency
                    maFillColor[3] = 1.0 - nTransparency / 100.0;
                }

                if( bStroke )
                {
                    maState.DeviceColor = rState.lineColor;

                    if( maState.DeviceColor.getLength() < 4 )
                        maState.DeviceColor.realloc( 4 );

                    // TODO(F1): Color management
                    // adapt fill color transparency
                    maState.DeviceColor[3] = 1.0 - nTransparency / 100.0;
                }
            }

            bool PolyPolyAction::render( uno::Reference< rendering::XCachedPrimitive >& rCachedPrimitive,
                                         const ::basegfx::B2DHomMatrix&                 rTransformation ) const
            {
                RTL_LOGFILE_CONTEXT( aLog, "::cppcanvas::internal::PolyPolyAction::render()" );
                RTL_LOGFILE_CONTEXT_TRACE1( aLog, "::cppcanvas::internal::PolyPolyAction: 0x%X", this );

                rendering::RenderState aLocalState( maState );
                ::canvas::tools::prependToRenderState(aLocalState, rTransformation);

#ifdef SPECIAL_DEBUG
                aLocalState.Clip.clear();
                aLocalState.DeviceColor =
                    ::vcl::unotools::colorToDoubleSequence( mpCanvas->getUNOCanvas()->getDevice(),
                                                            ::Color( 0x80FF0000 ) );

                if( maState.Clip.is() )
                    mpCanvas->getUNOCanvas()->fillPolyPolygon( maState.Clip,
                                                               mpCanvas->getViewState(),
                                                               aLocalState );

                aLocalState.DeviceColor = maState.DeviceColor;
#endif

                if( maFillColor.getLength() )
                {
                    // TODO(E3): Use DBO's finalizer here,
                    // fillPolyPolygon() might throw
                    const uno::Sequence< double > aTmpColor( aLocalState.DeviceColor );
                    aLocalState.DeviceColor = maFillColor;

                    rCachedPrimitive = mpCanvas->getUNOCanvas()->fillPolyPolygon( mxPolyPoly,
                                                                                mpCanvas->getViewState(),
                                                                                aLocalState );

                    aLocalState.DeviceColor = aTmpColor;
                }

                if( aLocalState.DeviceColor.getLength() )
                {
                    rCachedPrimitive = mpCanvas->getUNOCanvas()->drawPolyPolygon( mxPolyPoly,
                                                                                mpCanvas->getViewState(),
                                                                                aLocalState );
                }

                return true;
            }

            bool PolyPolyAction::render( const ::basegfx::B2DHomMatrix&	rTransformation,
                                         const Subset&					rSubset ) const
            {
                // TODO(F1): Split up poly-polygon into polygons, or even
                // line segments, when subsets are requested.

                // polygon only contains a single action, fail if subset
                // requests different range
                if( rSubset.mnSubsetBegin != 0 ||
                    rSubset.mnSubsetEnd != 1 )
                    return false;

                return CachedPrimitiveBase::render( rTransformation );
            }

            ::basegfx::B2DRange PolyPolyAction::getBounds( const ::basegfx::B2DHomMatrix&	rTransformation ) const
            {
                rendering::RenderState aLocalState( maState );
                ::canvas::tools::prependToRenderState(aLocalState, rTransformation);

                return tools::calcDevicePixelBounds(
                    maBounds,
                    mpCanvas->getViewState(),
                    aLocalState );
            }

            ::basegfx::B2DRange PolyPolyAction::getBounds( const ::basegfx::B2DHomMatrix&	rTransformation,
                                                           const Subset&					rSubset ) const
            {
                // TODO(F1): Split up poly-polygon into polygons, or even
                // line segments, when subsets are requested.

                // polygon only contains a single action, empty bounds
                // if subset requests different range
                if( rSubset.mnSubsetBegin != 0 ||
                    rSubset.mnSubsetEnd != 1 )
                    return ::basegfx::B2DRange();

                return getBounds( rTransformation );
            }

            sal_Int32 PolyPolyAction::getActionCount() const
            {
                // TODO(F1): Split up poly-polygon into polygons, or even
                // line segments, when subsets are requested.
                return 1;
            }

            // -------------------------------------------------------------------------------

            class TexturedPolyPolyAction : public CachedPrimitiveBase
            {
            public:
                TexturedPolyPolyAction( const ::basegfx::B2DPolyPolygon& rPoly,
                                        const CanvasSharedPtr&		     rCanvas,
                                        const OutDevState&			     rState,
                                        const rendering::Texture& 	     rTexture );

                virtual bool render( const ::basegfx::B2DHomMatrix& rTransformation,
                                     const Subset&					rSubset ) const;

                virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const;
                virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix&	rTransformation,
                                                       const Subset&					rSubset ) const;

                virtual sal_Int32 getActionCount() const;

            private:
                using Action::render;
                virtual bool render( uno::Reference< rendering::XCachedPrimitive >& rCachedPrimitive,
                                     const ::basegfx::B2DHomMatrix&                 rTransformation ) const;

                const uno::Reference< rendering::XPolyPolygon2D >	mxPolyPoly;
                const ::basegfx::B2DRectangle						maBounds;
                const CanvasSharedPtr								mpCanvas;

            	// stroke color is now implicit: the maState.DeviceColor member
                rendering::RenderState								maState;
                const rendering::Texture							maTexture;
            };

            TexturedPolyPolyAction::TexturedPolyPolyAction( const ::basegfx::B2DPolyPolygon& rPolyPoly,
                                                            const CanvasSharedPtr&		     rCanvas,
                                                            const OutDevState& 			     rState,
                                                            const rendering::Texture& 	     rTexture ) :
                CachedPrimitiveBase( rCanvas, true ),
                mxPolyPoly( ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( rCanvas->getUNOCanvas()->getDevice(), rPolyPoly) ),
                maBounds( ::basegfx::tools::getRange(rPolyPoly) ),
                mpCanvas( rCanvas ),
                maState(),
                maTexture( rTexture )
            {
                tools::initRenderState(maState,rState);
            }

            bool TexturedPolyPolyAction::render( uno::Reference< rendering::XCachedPrimitive >& rCachedPrimitive,
                                                 const ::basegfx::B2DHomMatrix&                 rTransformation ) const
            {
                RTL_LOGFILE_CONTEXT( aLog, "::cppcanvas::internal::PolyPolyAction::render()" );
                RTL_LOGFILE_CONTEXT_TRACE1( aLog, "::cppcanvas::internal::PolyPolyAction: 0x%X", this );

                rendering::RenderState aLocalState( maState );
                ::canvas::tools::prependToRenderState(aLocalState, rTransformation);

                uno::Sequence< rendering::Texture > aSeq(1);
                aSeq[0] = maTexture;

                rCachedPrimitive = mpCanvas->getUNOCanvas()->fillTexturedPolyPolygon( mxPolyPoly,
                                                                                mpCanvas->getViewState(),
                                                                                aLocalState,
                                                                                aSeq );
                return true;
            }

            bool TexturedPolyPolyAction::render( const ::basegfx::B2DHomMatrix&	rTransformation,
                                                 const Subset&					rSubset ) const
            {
                // TODO(F1): Split up poly-polygon into polygons, or even
                // line segments, when subsets are requested.

                // polygon only contains a single action, fail if subset
                // requests different range
                if( rSubset.mnSubsetBegin != 0 ||
                    rSubset.mnSubsetEnd != 1 )
                    return false;

                return CachedPrimitiveBase::render( rTransformation );
            }

            ::basegfx::B2DRange TexturedPolyPolyAction::getBounds( const ::basegfx::B2DHomMatrix&	rTransformation ) const
            {
                rendering::RenderState aLocalState( maState );
                ::canvas::tools::prependToRenderState(aLocalState, rTransformation);

                return tools::calcDevicePixelBounds(
                    maBounds,
                    mpCanvas->getViewState(),
                    aLocalState );
            }

            ::basegfx::B2DRange TexturedPolyPolyAction::getBounds( const ::basegfx::B2DHomMatrix&	rTransformation,
                                                                   const Subset&					rSubset ) const
            {
                // TODO(F1): Split up poly-polygon into polygons, or even
                // line segments, when subsets are requested.

                // polygon only contains a single action, empty bounds
                // if subset requests different range
                if( rSubset.mnSubsetBegin != 0 ||
                    rSubset.mnSubsetEnd != 1 )
                    return ::basegfx::B2DRange();

                return getBounds( rTransformation );
            }

            sal_Int32 TexturedPolyPolyAction::getActionCount() const
            {
                // TODO(F1): Split up poly-polygon into polygons, or even
                // line segments, when subsets are requested.
                return 1;
            }

            // -------------------------------------------------------------------------------

            class StrokedPolyPolyAction : public CachedPrimitiveBase
            {
            public:
                StrokedPolyPolyAction( const ::basegfx::B2DPolyPolygon&     rPoly,
                                       const CanvasSharedPtr&				rCanvas,
                                       const OutDevState&					rState,
                                       const rendering::StrokeAttributes&	rStrokeAttributes );

                virtual bool render( const ::basegfx::B2DHomMatrix& rTransformation,
                                     const Subset&					rSubset ) const;

                virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix& rTransformation ) const;
                virtual ::basegfx::B2DRange getBounds( const ::basegfx::B2DHomMatrix&	rTransformation,
                                                       const Subset&					rSubset ) const;

                virtual sal_Int32 getActionCount() const;

            private:
                using Action::render;
                virtual bool render( uno::Reference< rendering::XCachedPrimitive >& rCachedPrimitive,
                                     const ::basegfx::B2DHomMatrix&                 rTransformation ) const;

                const uno::Reference< rendering::XPolyPolygon2D >	mxPolyPoly;
                const ::basegfx::B2DRectangle						maBounds;
                const CanvasSharedPtr								mpCanvas;
                rendering::RenderState								maState;
                const rendering::StrokeAttributes					maStrokeAttributes;
            };

            StrokedPolyPolyAction::StrokedPolyPolyAction( const ::basegfx::B2DPolyPolygon&      rPolyPoly,
                                                          const CanvasSharedPtr&				rCanvas,
                                                          const OutDevState& 					rState,
                                                          const rendering::StrokeAttributes&	rStrokeAttributes ) :
                CachedPrimitiveBase( rCanvas, false ),
                mxPolyPoly( ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( rCanvas->getUNOCanvas()->getDevice(), rPolyPoly) ),
                maBounds( ::basegfx::tools::getRange(rPolyPoly) ),
                mpCanvas( rCanvas ),
                maState(),
                maStrokeAttributes( rStrokeAttributes )
            {
                tools::initRenderState(maState,rState);
                maState.DeviceColor = rState.lineColor;
            }

            bool StrokedPolyPolyAction::render( uno::Reference< rendering::XCachedPrimitive >& rCachedPrimitive,
                                                const ::basegfx::B2DHomMatrix&                 rTransformation ) const
            {
                RTL_LOGFILE_CONTEXT( aLog, "::cppcanvas::internal::PolyPolyAction::render()" );
                RTL_LOGFILE_CONTEXT_TRACE1( aLog, "::cppcanvas::internal::PolyPolyAction: 0x%X", this );

                rendering::RenderState aLocalState( maState );
                ::canvas::tools::prependToRenderState(aLocalState, rTransformation);

                rCachedPrimitive = mpCanvas->getUNOCanvas()->strokePolyPolygon( mxPolyPoly,
                                                                                mpCanvas->getViewState(),
                                                                                aLocalState,
                                                                                maStrokeAttributes );
                return true;
            }

            bool StrokedPolyPolyAction::render( const ::basegfx::B2DHomMatrix&	rTransformation,
                                                const Subset&					rSubset ) const
            {
                // TODO(F1): Split up poly-polygon into polygons, or even
                // line segments, when subsets are requested.

                // polygon only contains a single action, fail if subset
                // requests different range
                if( rSubset.mnSubsetBegin != 0 ||
                    rSubset.mnSubsetEnd != 1 )
                    return false;

                return CachedPrimitiveBase::render( rTransformation );
            }

            ::basegfx::B2DRange StrokedPolyPolyAction::getBounds( const ::basegfx::B2DHomMatrix&	rTransformation ) const
            {
                rendering::RenderState aLocalState( maState );
                ::canvas::tools::prependToRenderState(aLocalState, rTransformation);

                return tools::calcDevicePixelBounds(
                    maBounds,
                    mpCanvas->getViewState(),
                    aLocalState );
            }

            ::basegfx::B2DRange StrokedPolyPolyAction::getBounds( const ::basegfx::B2DHomMatrix&	rTransformation,
                                                                  const Subset&					rSubset ) const
            {
                // TODO(F1): Split up poly-polygon into polygons, or even
                // line segments, when subsets are requested.

                // polygon only contains a single action, empty bounds
                // if subset requests different range
                if( rSubset.mnSubsetBegin != 0 ||
                    rSubset.mnSubsetEnd != 1 )
                    return ::basegfx::B2DRange();

                return getBounds( rTransformation );
            }

            sal_Int32 StrokedPolyPolyAction::getActionCount() const
            {
                // TODO(F1): Split up poly-polygon into polygons, or even
                // line segments, when subsets are requested.
                return 1;
            }
        }

        ActionSharedPtr PolyPolyActionFactory::createPolyPolyAction( const ::basegfx::B2DPolyPolygon& rPoly,
                                                                     const CanvasSharedPtr&           rCanvas,
                                                                     const OutDevState&               rState	)
        {
            OSL_ENSURE( rState.isLineColorSet || rState.isFillColorSet,
                        "PolyPolyActionFactory::createPolyPolyAction() with empty line and fill color" );
            return ActionSharedPtr( new PolyPolyAction( rPoly, rCanvas, rState,
                                                        rState.isFillColorSet,
                                                        rState.isLineColorSet ) );
        }

        ActionSharedPtr PolyPolyActionFactory::createPolyPolyAction( const ::basegfx::B2DPolyPolygon&   rPoly,
                                                                     const CanvasSharedPtr&             rCanvas,
                                                                     const OutDevState&                 rState,
                                                                     const rendering::Texture&          rTexture )
        {
            return ActionSharedPtr( new TexturedPolyPolyAction( rPoly, rCanvas, rState, rTexture ) );
        }

        ActionSharedPtr PolyPolyActionFactory::createLinePolyPolyAction( const ::basegfx::B2DPolyPolygon& rPoly,
                                                                         const CanvasSharedPtr&           rCanvas,
                                                                         const OutDevState&               rState )
        {
            OSL_ENSURE( rState.isLineColorSet,
                        "PolyPolyActionFactory::createLinePolyPolyAction() called with empty line color" );

            return ActionSharedPtr( new PolyPolyAction( rPoly, rCanvas, rState,
                                                        false,
                                                        rState.isLineColorSet ) );
        }

        ActionSharedPtr PolyPolyActionFactory::createPolyPolyAction( const ::basegfx::B2DPolyPolygon&   rPoly,
                                                                     const CanvasSharedPtr&				rCanvas,
                                                                     const OutDevState&					rState,
                                                                     const rendering::StrokeAttributes& rStrokeAttributes )
        {
            OSL_ENSURE( rState.isLineColorSet,
                        "PolyPolyActionFactory::createPolyPolyAction() for strokes called with empty line color" );
            return ActionSharedPtr( new StrokedPolyPolyAction( rPoly, rCanvas, rState, rStrokeAttributes ) );
        }

        ActionSharedPtr PolyPolyActionFactory::createPolyPolyAction( const ::basegfx::B2DPolyPolygon& rPoly,
                                                                     const CanvasSharedPtr&           rCanvas,
                                                                     const OutDevState&               rState,
                                                                     int                              nTransparency 	)
        {
            OSL_ENSURE( rState.isLineColorSet || rState.isFillColorSet,
                        "PolyPolyActionFactory::createPolyPolyAction() with empty line and fill color" );
            return ActionSharedPtr( new PolyPolyAction( rPoly, rCanvas, rState,
                                                        rState.isFillColorSet,
                                                        rState.isLineColorSet,
                                                        nTransparency ) );
        }

    }
}


// Distributed under the MIT License.
// See LICENSE.txt for details.

#pragma once

#include <boost/functional/hash.hpp>
#include <cstddef>
#include <optional>
#include <type_traits>
#include <utility>
#include <vector>

#include "DataStructures/DataBox/DataBox.hpp"
#include "DataStructures/DataBox/PrefixHelpers.hpp"
#include "DataStructures/DataBox/Prefixes.hpp"
#include "DataStructures/DataVector.hpp"
#include "DataStructures/FixedHashMap.hpp"
#include "DataStructures/Index.hpp"
#include "DataStructures/Tensor/Tensor.hpp"
#include "DataStructures/Variables.hpp"
#include "DataStructures/VariablesTag.hpp"
#include "Domain/Structure/Direction.hpp"
#include "Domain/Structure/Element.hpp"
#include "Domain/Structure/ElementId.hpp"
#include "Domain/Structure/MaxNumberOfNeighbors.hpp"
#include "Domain/Tags.hpp"
#include "Domain/TagsTimeDependent.hpp"
#include "Evolution/BoundaryCorrectionTags.hpp"
#include "Evolution/DgSubcell/Projection.hpp"
#include "Evolution/DgSubcell/Reconstruction.hpp"
#include "Evolution/DgSubcell/ReconstructionMethod.hpp"
#include "Evolution/DgSubcell/SubcellOptions.hpp"
#include "Evolution/DgSubcell/Tags/Mesh.hpp"
#include "Evolution/DgSubcell/Tags/NeighborData.hpp"
#include "Evolution/DgSubcell/Tags/SubcellOptions.hpp"
#include "Evolution/DiscontinuousGalerkin/Actions/NormalCovectorAndMagnitude.hpp"
#include "Evolution/DiscontinuousGalerkin/Actions/PackageDataImpl.hpp"
#include "Evolution/Systems/NewtonianEuler/BoundaryCorrections/BoundaryCorrection.hpp"
#include "Evolution/Systems/NewtonianEuler/BoundaryCorrections/Factory.hpp"
#include "Evolution/Systems/NewtonianEuler/FiniteDifference/Reconstructor.hpp"
#include "Evolution/Systems/NewtonianEuler/FiniteDifference/Tag.hpp"
#include "Evolution/Systems/NewtonianEuler/Subcell/ComputeFluxes.hpp"
#include "Evolution/Systems/NewtonianEuler/System.hpp"
#include "Evolution/Systems/NewtonianEuler/Tags.hpp"
#include "NumericalAlgorithms/Spectral/Mesh.hpp"
#include "Parallel/Tags/Metavariables.hpp"
#include "PointwiseFunctions/Hydro/EquationsOfState/EquationOfState.hpp"
#include "PointwiseFunctions/Hydro/Tags.hpp"
#include "Utilities/CallWithDynamicType.hpp"
#include "Utilities/Gsl.hpp"
#include "Utilities/TMPL.hpp"

namespace NewtonianEuler::subcell {
/*!
 * \brief On elements using DG, reconstructs the interface data from a
 * neighboring element doing subcell.
 *
 * The neighbor's packaged data needed by the boundary correction is computed
 * and returned so that it can be used for solving the Riemann problem on the
 * interfaces.
 *
 * Note that for strict conservation the Riemann solve should be done on the
 * subcells, with the correction being projected back to the DG interface.
 * However, in practice such strict conservation doesn't seem to be necessary
 * and can be explained by that we only need strict conservation at shocks, and
 * if one element is doing DG, then we aren't at a shock.
 */
struct NeighborPackagedData {
  template <size_t Dim, typename DbTagsList>
  static FixedHashMap<maximum_number_of_neighbors(Dim),
                      std::pair<Direction<Dim>, ElementId<Dim>>,
                      std::vector<double>,
                      boost::hash<std::pair<Direction<Dim>, ElementId<Dim>>>>
  apply(const db::DataBox<DbTagsList>& box,
        const std::vector<std::pair<Direction<Dim>, ElementId<Dim>>>&
            mortars_to_reconstruct_to) {
    using system =
        typename std::decay_t<decltype(db::get<Parallel::Tags::Metavariables>(
            box))>::system;
    using evolved_vars_tag = typename system::variables_tag;
    using evolved_vars_tags = typename evolved_vars_tag::tags_list;
    using prim_tags = typename system::primitive_variables_tag::tags_list;
    using fluxes_tags = db::wrap_tags_in<::Tags::Flux, evolved_vars_tags,
                                         tmpl::size_t<Dim>, Frame::Inertial>;

    ASSERT(not db::get<domain::Tags::MeshVelocity<Dim>>(box).has_value(),
           "Haven't yet added support for moving mesh to DG-subcell. This "
           "should be easy to generalize, but we will want to consider "
           "storing the mesh velocity on the faces instead of "
           "re-slicing/projecting.");

    FixedHashMap<maximum_number_of_neighbors(Dim),
                 std::pair<Direction<Dim>, ElementId<Dim>>, std::vector<double>,
                 boost::hash<std::pair<Direction<Dim>, ElementId<Dim>>>>
        neighbor_package_data{};

    const auto& neighbor_subcell_data = db::get<
        evolution::dg::subcell::Tags::NeighborDataForReconstruction<Dim>>(box);
    const Mesh<Dim>& subcell_mesh =
        db::get<evolution::dg::subcell::Tags::Mesh<Dim>>(box);
    const Mesh<Dim>& dg_mesh = db::get<domain::Tags::Mesh<Dim>>(box);
    const auto& subcell_options =
        db::get<evolution::dg::subcell::Tags::SubcellOptions>(box);

    const auto volume_prims = evolution::dg::subcell::fd::project(
        db::get<typename system::primitive_variables_tag>(box), dg_mesh,
        subcell_mesh.extents());

    const auto& recons =
        db::get<NewtonianEuler::fd::Tags::Reconstructor<Dim>>(box);
    const auto& boundary_correction =
        db::get<evolution::Tags::BoundaryCorrection<system>>(box);
    using derived_boundary_corrections =
        typename std::decay_t<decltype(boundary_correction)>::creatable_classes;
    tmpl::for_each<
        derived_boundary_corrections>([&box, &boundary_correction, &dg_mesh,
                                       &mortars_to_reconstruct_to,
                                       &neighbor_package_data,
                                       &neighbor_subcell_data, &recons,
                                       &subcell_mesh, &subcell_options,
                                       &volume_prims](
                                          auto derived_correction_v) {
      using DerivedCorrection = tmpl::type_from<decltype(derived_correction_v)>;
      if (typeid(boundary_correction) == typeid(DerivedCorrection)) {
        using dg_package_data_temporary_tags =
            typename DerivedCorrection::dg_package_data_temporary_tags;
        using dg_package_data_argument_tags =
            tmpl::append<evolved_vars_tags, prim_tags, fluxes_tags,
                         dg_package_data_temporary_tags>;

        const auto& element = db::get<domain::Tags::Element<Dim>>(box);
        const auto& eos = get<hydro::Tags::EquationOfStateBase>(box);

        using dg_package_field_tags =
            typename DerivedCorrection::dg_package_field_tags;
        Variables<dg_package_data_argument_tags> vars_on_face;
        Variables<dg_package_field_tags> packaged_data;
        for (const auto& mortar_id : mortars_to_reconstruct_to) {
          const Direction<Dim>& direction = mortar_id.first;

          Index<Dim> extents = subcell_mesh.extents();
          // Switch to face-centered instead of cell-centered points on the FD.
          // There are num_cell_centered+1 face-centered points.
          ++extents[direction.dimension()];

          // Computed prims and cons on face via reconstruction
          const size_t num_face_pts = subcell_mesh.extents()
                                          .slice_away(direction.dimension())
                                          .product();
          vars_on_face.initialize(num_face_pts);

          call_with_dynamic_type<void,
                                 typename NewtonianEuler::fd::Reconstructor<
                                     Dim>::creatable_classes>(
              &recons, [&element, &eos, &mortar_id, &neighbor_subcell_data,
                        &subcell_mesh, &vars_on_face,
                        &volume_prims](const auto& reconstructor) {
                reconstructor->reconstruct_fd_neighbor(
                    make_not_null(&vars_on_face), volume_prims, eos, element,
                    neighbor_subcell_data, subcell_mesh, mortar_id.first);
              });

          NewtonianEuler::subcell::compute_fluxes<Dim>(
              make_not_null(&vars_on_face));

          tnsr::i<DataVector, Dim, Frame::Inertial> normal_covector = get<
              evolution::dg::Tags::NormalCovector<Dim>>(
              *db::get<evolution::dg::Tags::NormalCovectorAndMagnitude<Dim>>(
                   box)
                   .at(mortar_id.first));
          for (auto& t : normal_covector) {
            t *= -1.0;
          }
          if constexpr (Dim > 1) {
            const auto dg_normal_covector = normal_covector;
            for (size_t i = 0; i < Dim; ++i) {
              normal_covector.get(i) = evolution::dg::subcell::fd::project(
                  dg_normal_covector.get(i),
                  dg_mesh.slice_away(mortar_id.first.dimension()),
                  subcell_mesh.extents().slice_away(
                      mortar_id.first.dimension()));
            }
          }

          // Compute the packaged data
          packaged_data.initialize(num_face_pts);
          using dg_package_data_projected_tags = tmpl::append<
              evolved_vars_tags, fluxes_tags, dg_package_data_temporary_tags,
              typename DerivedCorrection::dg_package_data_primitive_tags>;
          evolution::dg::Actions::detail::dg_package_data<system>(
              make_not_null(&packaged_data),
              dynamic_cast<const DerivedCorrection&>(boundary_correction),
              vars_on_face, normal_covector, {std::nullopt}, box,
              typename DerivedCorrection::dg_package_data_volume_tags{},
              dg_package_data_projected_tags{});

          if constexpr (Dim == 1) {
            (void)dg_mesh;
            (void)subcell_options;
            neighbor_package_data[mortar_id] = std::vector<double>{
                packaged_data.data(),
                packaged_data.data() + packaged_data.size()};
          } else {
            // Reconstruct the DG solution.
            // Really we should be solving the boundary correction and
            // then reconstructing, but away from a shock this doesn't
            // matter.
            auto dg_packaged_data = evolution::dg::subcell::fd::reconstruct(
                packaged_data, dg_mesh.slice_away(mortar_id.first.dimension()),
                subcell_mesh.extents().slice_away(mortar_id.first.dimension()),
                subcell_options.reconstruction_method());
            neighbor_package_data[mortar_id] = std::vector<double>{
                dg_packaged_data.data(),
                dg_packaged_data.data() + dg_packaged_data.size()};
          }
        }
      }
    });

    return neighbor_package_data;
  }
};
}  // namespace NewtonianEuler::subcell


//          Copyright Gavin Band 2008 - 2012.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          http://www.boost.org/LICENSE_1_0.txt)

#ifndef QCTOOL_CROSS_DATA_SET_HAPLOTYPE_COMPARISON_COMPUTATION_HPP
#define QCTOOL_CROSS_DATA_SET_HAPLOTYPE_COMPARISON_COMPUTATION_HPP

#include <deque>
#include <vector>
#include <Eigen/Core>
#include "genfile/CohortIndividualSource.hpp"
#include "genfile/SNPDataSource.hpp"
#include "components/SNPSummaryComponent/SNPSummaryComputation.hpp"
#include "components/SNPSummaryComponent/CrossDataSetConcordanceComputation.hpp"

namespace snp_stats {
	struct CrossDataSetHaplotypeComparisonComputation: public CrossDataSetComparison
	{
	public:
		CrossDataSetHaplotypeComparisonComputation(
			genfile::CohortIndividualSource const& m_samples,
			std::string const& main_dataset_sample_id_column
		) ;

		void set_alternate_dataset(
			genfile::CohortIndividualSource::UniquePtr samples,
			std::string const& comparison_dataset_sample_id_column,
			genfile::SNPDataSource::UniquePtr snps
		) ;

		void set_comparer( genfile::VariantIdentifyingData::CompareFields const& comparer ) ;
		void set_match_alleles() ;

		void operator()( VariantIdentifyingData const&, Genotypes const&, Ploidy const&, genfile::VariantDataReader&, ResultCallback ) ;
		std::string get_summary( std::string const& prefix = "", std::size_t column_width = 20 ) const ;

	private:
		CrossDataSetSampleMapper m_sample_mapper ;

		genfile::VariantIdentifyingData::CompareFields m_comparer ;
		bool m_match_alleles ;
		genfile::SNPDataSource::UniquePtr m_alt_dataset_snps ;

		double const m_call_threshhold ;
		Eigen::MatrixXd m_haplotypes1 ;
		Eigen::MatrixXd m_haplotypes2 ;
		Eigen::MatrixXd m_nonmissingness1 ;
		Eigen::MatrixXd m_nonmissingness2 ;

		Eigen::VectorXd m_relative_phase ;
	} ;
}

#endif


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

#include "error_rate.hpp"

#include <numeric>
#include <algorithm>
#include <iterator>
#include <map>
#include <cstddef>

#include <boost/variant.hpp>

#include "basics/cigar_string.hpp"
#include "basics/aligned_read.hpp"
#include "core/types/haplotype.hpp"
#include "io/variant/vcf_record.hpp"
#include "../facets/samples.hpp"
#include "../facets/read_assignments.hpp"

namespace octopus { namespace csr {

const std::string ErrorRate::name_ = "ER";

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

Measure::ResultType ErrorRate::get_default_result() const
{
    return std::vector<boost::optional<double>> {};
}

namespace {

boost::optional<double>
compute_error_rate(const Facet::SupportMaps::HaplotypeSupportMaps& assignments, const GenomicRegion& region) noexcept
{
    std::size_t error_bases {0}, total_bases {0};
    for (const auto& p : assignments.assigned_wrt_haplotype) {
        for (const auto& read : overlap_range(p.second, region)) {
            error_bases += sum_non_matches(read.cigar());
            total_bases += sequence_size(read);
        }
    }
    for (const auto& read : overlap_range(assignments.ambiguous_wrt_haplotype, region)) {
        error_bases += sum_non_matches(read.read.cigar());
        total_bases += sequence_size(read.read);
    }
    if (total_bases > 0) {
        return static_cast<double>(error_bases) / total_bases;
    } else {
        return boost::none;
    }
}

} // namespace

Measure::ResultType ErrorRate::do_evaluate(const VcfRecord& call, const FacetMap& facets) const
{
    const auto& samples = get_value<Samples>(facets.at("Samples"));
    const auto& assignments = get_value<ReadAssignments>(facets.at("ReadAssignments")).haplotypes;
    std::vector<boost::optional<double>> result {};
    result.reserve(samples.size());
    for (const auto& sample : samples) {
        result.push_back(compute_error_rate(assignments.at(sample), mapped_region(call)));
    }
    return result;
}

Measure::ResultCardinality ErrorRate::do_cardinality() const noexcept
{
    return ResultCardinality::samples;
}

const std::string& ErrorRate::do_name() const
{
    return name_;
}

std::string ErrorRate::do_describe() const
{
    return "Error rate in reads overlapping the site";
}

std::vector<std::string> ErrorRate::do_requirements() const
{
    return {"Samples", "ReadAssignments"};
}

} // namespace csr
} // namespace octopus