#include <language/modules/SchemeModule.hpp>

#include <language/modules/BinaryOperatorRegisterForVh.hpp>
#include <language/utils/BinaryOperatorProcessorBuilder.hpp>
#include <language/utils/BuiltinFunctionEmbedder.hpp>
#include <language/utils/TypeDescriptor.hpp>
#include <mesh/Mesh.hpp>
#include <scheme/AcousticSolver.hpp>
#include <scheme/DirichletBoundaryConditionDescriptor.hpp>
#include <scheme/DiscreteFunctionDescriptorP0.hpp>
#include <scheme/DiscreteFunctionInterpoler.hpp>
#include <scheme/DiscreteFunctionUtils.hpp>
#include <scheme/DiscreteFunctionVectorInterpoler.hpp>
#include <scheme/FourierBoundaryConditionDescriptor.hpp>
#include <scheme/FreeBoundaryConditionDescriptor.hpp>
#include <scheme/IBoundaryConditionDescriptor.hpp>
#include <scheme/IBoundaryDescriptor.hpp>
#include <scheme/IDiscreteFunction.hpp>
#include <scheme/IDiscreteFunctionDescriptor.hpp>
#include <scheme/NamedBoundaryDescriptor.hpp>
#include <scheme/NeumannBoundaryConditionDescriptor.hpp>
#include <scheme/NumberedBoundaryDescriptor.hpp>
#include <scheme/SymmetryBoundaryConditionDescriptor.hpp>

#include <scheme/PerfectGas.hpp>

#include <memory>

SchemeModule::SchemeModule()
{
  this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const IDiscreteFunction>>);
  this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const IDiscreteFunctionDescriptor>>);

  this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const IBoundaryDescriptor>>);
  this->_addTypeDescriptor(ast_node_data_type_from<std::shared_ptr<const IBoundaryConditionDescriptor>>);

  this->_addBuiltinFunction("P0", std::make_shared<
                                    BuiltinFunctionEmbedder<std::shared_ptr<const IDiscreteFunctionDescriptor>()>>(
                                    []() -> std::shared_ptr<const IDiscreteFunctionDescriptor> {
                                      return std::make_shared<DiscreteFunctionDescriptorP0>();
                                    }

                                    ));

  this->_addBuiltinFunction(
    "interpolate",
    std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<
      const IDiscreteFunction>(std::shared_ptr<const IMesh>, std::shared_ptr<const IDiscreteFunctionDescriptor>,
                               const std::vector<FunctionSymbolId>&)>>(
      [](std::shared_ptr<const IMesh> mesh,
         std::shared_ptr<const IDiscreteFunctionDescriptor> discrete_function_descriptor,
         const std::vector<FunctionSymbolId>& function_id_list) -> std::shared_ptr<const IDiscreteFunction> {
        return DiscreteFunctionVectorInterpoler{mesh, discrete_function_descriptor, function_id_list}.interpolate();
      }

      ));

  this->_addBuiltinFunction(
    "interpolate",
    std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<
      const IDiscreteFunction>(std::shared_ptr<const IMesh>, std::shared_ptr<const IDiscreteFunctionDescriptor>,
                               const FunctionSymbolId&)>>(
      [](std::shared_ptr<const IMesh> mesh,
         std::shared_ptr<const IDiscreteFunctionDescriptor> discrete_function_descriptor,
         const FunctionSymbolId& function_id) -> std::shared_ptr<const IDiscreteFunction> {
        return DiscreteFunctionInterpoler{mesh, discrete_function_descriptor, function_id}.interpolate();
      }

      ));

  this->_addBuiltinFunction("boundaryName",
                            std::make_shared<
                              BuiltinFunctionEmbedder<std::shared_ptr<const IBoundaryDescriptor>(const std::string&)>>(

                              [](const std::string& boundary_name) -> std::shared_ptr<const IBoundaryDescriptor> {
                                return std::make_shared<NamedBoundaryDescriptor>(boundary_name);
                              }

                              ));

  this->_addBuiltinFunction("boundaryTag",
                            std::make_shared<
                              BuiltinFunctionEmbedder<std::shared_ptr<const IBoundaryDescriptor>(int64_t)>>(

                              [](int64_t boundary_tag) -> std::shared_ptr<const IBoundaryDescriptor> {
                                return std::make_shared<NumberedBoundaryDescriptor>(boundary_tag);
                              }

                              ));

  this
    ->_addBuiltinFunction("symmetry",
                          std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<const IBoundaryConditionDescriptor>(
                            std::shared_ptr<const IBoundaryDescriptor>)>>(

                            [](std::shared_ptr<const IBoundaryDescriptor> boundary)
                              -> std::shared_ptr<const IBoundaryConditionDescriptor> {
                              return std::make_shared<SymmetryBoundaryConditionDescriptor>(boundary);
                            }

                            ));

  this->_addBuiltinFunction("pressure",
                            std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<
                              const IBoundaryConditionDescriptor>(std::shared_ptr<const IBoundaryDescriptor>,
                                                                  const FunctionSymbolId&)>>(

                              [](std::shared_ptr<const IBoundaryDescriptor> boundary,
                                 const FunctionSymbolId& pressure_id)
                                -> std::shared_ptr<const IBoundaryConditionDescriptor> {
                                return std::make_shared<DirichletBoundaryConditionDescriptor>("pressure", boundary,
                                                                                              pressure_id);
                              }

                              ));

  this->_addBuiltinFunction("velocity",
                            std::make_shared<BuiltinFunctionEmbedder<std::shared_ptr<
                              const IBoundaryConditionDescriptor>(std::shared_ptr<const IBoundaryDescriptor>,
                                                                  const FunctionSymbolId&)>>(

                              [](std::shared_ptr<const IBoundaryDescriptor> boundary,
                                 const FunctionSymbolId& velocity_id)
                                -> std::shared_ptr<const IBoundaryConditionDescriptor> {
                                return std::make_shared<DirichletBoundaryConditionDescriptor>("velocity", boundary,
                                                                                              velocity_id);
                              }

                              ));

  this
    ->_addBuiltinFunction("perfect_gas_epsilon_from_rho_p_gamma",
                          std::make_shared<BuiltinFunctionEmbedder<
                            std::shared_ptr<const IDiscreteFunction>(const std::shared_ptr<const IDiscreteFunction>&,
                                                                     const std::shared_ptr<const IDiscreteFunction>&,
                                                                     const std::shared_ptr<const IDiscreteFunction>&)>>(

                            [](const std::shared_ptr<const IDiscreteFunction>& rho,
                               const std::shared_ptr<const IDiscreteFunction>& p,
                               const std::shared_ptr<const IDiscreteFunction>& gamma)
                              -> std::shared_ptr<const IDiscreteFunction> {
                              return perfect_gas::epsilonFromRhoPGamma(rho, p, gamma);
                            }

                            ));

  this
    ->_addBuiltinFunction("perfect_gas_p_from_rho_epsilon_gamma",
                          std::make_shared<BuiltinFunctionEmbedder<
                            std::shared_ptr<const IDiscreteFunction>(const std::shared_ptr<const IDiscreteFunction>&,
                                                                     const std::shared_ptr<const IDiscreteFunction>&,
                                                                     const std::shared_ptr<const IDiscreteFunction>&)>>(

                            [](const std::shared_ptr<const IDiscreteFunction>& rho,
                               const std::shared_ptr<const IDiscreteFunction>& epsilon,
                               const std::shared_ptr<const IDiscreteFunction>& gamma)
                              -> std::shared_ptr<const IDiscreteFunction> {
                              return perfect_gas::pFromRhoEpsilonGamma(rho, epsilon, gamma);
                            }

                            ));

  this
    ->_addBuiltinFunction("perfect_gas_sound_speed",
                          std::make_shared<BuiltinFunctionEmbedder<
                            std::shared_ptr<const IDiscreteFunction>(const std::shared_ptr<const IDiscreteFunction>&,
                                                                     const std::shared_ptr<const IDiscreteFunction>&,
                                                                     const std::shared_ptr<const IDiscreteFunction>&)>>(

                            [](const std::shared_ptr<const IDiscreteFunction>& rho,
                               const std::shared_ptr<const IDiscreteFunction>& p,
                               const std::shared_ptr<const IDiscreteFunction>& gamma)
                              -> std::shared_ptr<const IDiscreteFunction> {
                              return perfect_gas::soundSpeed(rho, p, gamma);
                            }

                            ));

  this
    ->_addBuiltinFunction("E_from_epsilon_u",
                          std::make_shared<BuiltinFunctionEmbedder<
                            std::shared_ptr<const IDiscreteFunction>(const std::shared_ptr<const IDiscreteFunction>&,
                                                                     const std::shared_ptr<const IDiscreteFunction>&)>>(

                            [](const std::shared_ptr<const IDiscreteFunction>& epsilon,
                               const std::shared_ptr<const IDiscreteFunction>& u)
                              -> std::shared_ptr<const IDiscreteFunction> {
                              return perfect_gas::totalEnergyFromEpsilonU(epsilon, u);
                            }

                            ));

  this
    ->_addBuiltinFunction("epsilon_from_E_u",
                          std::make_shared<BuiltinFunctionEmbedder<
                            std::shared_ptr<const IDiscreteFunction>(const std::shared_ptr<const IDiscreteFunction>&,
                                                                     const std::shared_ptr<const IDiscreteFunction>&)>>(

                            [](const std::shared_ptr<const IDiscreteFunction>& E,
                               const std::shared_ptr<const IDiscreteFunction>& u)
                              -> std::shared_ptr<const IDiscreteFunction> {
                              return perfect_gas::epsilonFromTotalEnergyU(E, u);
                            }

                            ));

  this->_addBuiltinFunction("glace_solver",
                            std::make_shared<BuiltinFunctionEmbedder<std::tuple<
                              std::shared_ptr<const IMesh>, std::shared_ptr<const IDiscreteFunction>,
                              std::shared_ptr<const IDiscreteFunction>,
                              std::shared_ptr<const IDiscreteFunction>>(const std::shared_ptr<const IDiscreteFunction>&,
                                                                        const std::shared_ptr<const IDiscreteFunction>&,
                                                                        const std::shared_ptr<const IDiscreteFunction>&,
                                                                        const std::shared_ptr<const IDiscreteFunction>&,
                                                                        const std::shared_ptr<const IDiscreteFunction>&,
                                                                        const std::vector<std::shared_ptr<
                                                                          const IBoundaryConditionDescriptor>>&,
                                                                        const double&)>>(

                              [](const std::shared_ptr<const IDiscreteFunction>& rho,
                                 const std::shared_ptr<const IDiscreteFunction>& u,
                                 const std::shared_ptr<const IDiscreteFunction>& E,
                                 const std::shared_ptr<const IDiscreteFunction>& c,
                                 const std::shared_ptr<const IDiscreteFunction>& p,
                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
                                   bc_descriptor_list,
                                 const double& dt)
                                -> std::tuple<std::shared_ptr<const IMesh>, std::shared_ptr<const IDiscreteFunction>,
                                              std::shared_ptr<const IDiscreteFunction>,
                                              std::shared_ptr<const IDiscreteFunction>> {
                                return AcousticSolverHandler{AcousticSolverHandler::SolverType::Glace,
                                                             rho,
                                                             c,
                                                             u,
                                                             p,
                                                             bc_descriptor_list}
                                  .solver()
                                  .apply(dt, rho, u, E);
                              }

                              ));

  this->_addBuiltinFunction("eucclhyd_solver",
                            std::make_shared<BuiltinFunctionEmbedder<std::tuple<
                              std::shared_ptr<const IMesh>, std::shared_ptr<const IDiscreteFunction>,
                              std::shared_ptr<const IDiscreteFunction>,
                              std::shared_ptr<const IDiscreteFunction>>(const std::shared_ptr<const IDiscreteFunction>&,
                                                                        const std::shared_ptr<const IDiscreteFunction>&,
                                                                        const std::shared_ptr<const IDiscreteFunction>&,
                                                                        const std::shared_ptr<const IDiscreteFunction>&,
                                                                        const std::shared_ptr<const IDiscreteFunction>&,
                                                                        const std::vector<std::shared_ptr<
                                                                          const IBoundaryConditionDescriptor>>&,
                                                                        const double&)>>(

                              [](const std::shared_ptr<const IDiscreteFunction>& rho,
                                 const std::shared_ptr<const IDiscreteFunction>& u,
                                 const std::shared_ptr<const IDiscreteFunction>& E,
                                 const std::shared_ptr<const IDiscreteFunction>& c,
                                 const std::shared_ptr<const IDiscreteFunction>& p,
                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
                                   bc_descriptor_list,
                                 const double& dt)
                                -> std::tuple<std::shared_ptr<const IMesh>, std::shared_ptr<const IDiscreteFunction>,
                                              std::shared_ptr<const IDiscreteFunction>,
                                              std::shared_ptr<const IDiscreteFunction>> {
                                return AcousticSolverHandler{AcousticSolverHandler::SolverType::Eucclhyd,
                                                             rho,
                                                             c,
                                                             u,
                                                             p,
                                                             bc_descriptor_list}
                                  .solver()
                                  .apply(dt, rho, u, E);
                              }

                              ));

  this
    ->_addBuiltinFunction("lagrangian",
                          std::make_shared<BuiltinFunctionEmbedder<
                            std::shared_ptr<const IDiscreteFunction>(const std::shared_ptr<const IMesh>&,
                                                                     const std::shared_ptr<const IDiscreteFunction>&)>>(

                            [](const std::shared_ptr<const IMesh>& mesh,
                               const std::shared_ptr<const IDiscreteFunction>& v)
                              -> std::shared_ptr<const IDiscreteFunction> { return shallowCopy(mesh, v); }

                            ));

  this->_addBuiltinFunction("acoustic_dt",
                            std::make_shared<
                              BuiltinFunctionEmbedder<double(const std::shared_ptr<const IDiscreteFunction>&)>>(

                              [](const std::shared_ptr<const IDiscreteFunction>& c) -> double { return acoustic_dt(c); }

                              ));
}

void
SchemeModule::registerOperators() const
{
  BinaryOperatorRegisterForVh{};
}