#include <language/modules/SchemeModule.hpp>

#include <language/algorithms/AcousticSolverAlgorithm.hpp>
#include <language/utils/BuiltinFunctionEmbedder.hpp>
#include <language/utils/TypeDescriptor.hpp>
#include <mesh/Mesh.hpp>
#include <scheme/DirichletBoundaryConditionDescriptor.hpp>
#include <scheme/FourierBoundaryConditionDescriptor.hpp>
#include <scheme/FreeBoundaryConditionDescriptor.hpp>
#include <scheme/IBoundaryConditionDescriptor.hpp>
#include <scheme/IBoundaryDescriptor.hpp>
#include <scheme/NamedBoundaryDescriptor.hpp>
#include <scheme/NeumannBoundaryConditionDescriptor.hpp>
#include <scheme/NumberedBoundaryDescriptor.hpp>
#include <scheme/SymmetryBoundaryConditionDescriptor.hpp>

#include <memory>

SchemeModule::SchemeModule()
{
  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("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("glace",
                            std::make_shared<BuiltinFunctionEmbedder<
                              void(std::shared_ptr<const IMesh>,
                                   const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
                                   const FunctionSymbolId&, const FunctionSymbolId&, const FunctionSymbolId&)>>(

                              [](std::shared_ptr<const IMesh> p_mesh,
                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
                                   bc_descriptor_list,
                                 const FunctionSymbolId& rho_id, const FunctionSymbolId& u_id,
                                 const FunctionSymbolId& p_id) -> void {
                                switch (p_mesh->dimension()) {
                                case 1: {
                                  AcousticSolverAlgorithm<1>{AcousticSolverType::Glace,
                                                             p_mesh,
                                                             bc_descriptor_list,
                                                             rho_id,
                                                             u_id,
                                                             p_id};
                                  break;
                                }
                                case 2: {
                                  AcousticSolverAlgorithm<2>{AcousticSolverType::Glace,
                                                             p_mesh,
                                                             bc_descriptor_list,
                                                             rho_id,
                                                             u_id,
                                                             p_id};
                                  break;
                                }
                                case 3: {
                                  AcousticSolverAlgorithm<3>{AcousticSolverType::Glace,
                                                             p_mesh,
                                                             bc_descriptor_list,
                                                             rho_id,
                                                             u_id,
                                                             p_id};
                                  break;
                                }
                                default: {
                                  throw UnexpectedError("invalid mesh dimension");
                                }
                                }
                              }

                              ));

  this->_addBuiltinFunction("eucclhyd",
                            std::make_shared<BuiltinFunctionEmbedder<
                              void(std::shared_ptr<const IMesh>,
                                   const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&,
                                   const FunctionSymbolId&, const FunctionSymbolId&, const FunctionSymbolId&)>>(

                              [](std::shared_ptr<const IMesh> p_mesh,
                                 const std::vector<std::shared_ptr<const IBoundaryConditionDescriptor>>&
                                   bc_descriptor_list,
                                 const FunctionSymbolId& rho_id, const FunctionSymbolId& u_id,
                                 const FunctionSymbolId& p_id) -> void {
                                switch (p_mesh->dimension()) {
                                case 1: {
                                  AcousticSolverAlgorithm<1>{AcousticSolverType::Eucclhyd,
                                                             p_mesh,
                                                             bc_descriptor_list,
                                                             rho_id,
                                                             u_id,
                                                             p_id};
                                  break;
                                }
                                case 2: {
                                  AcousticSolverAlgorithm<2>{AcousticSolverType::Eucclhyd,
                                                             p_mesh,
                                                             bc_descriptor_list,
                                                             rho_id,
                                                             u_id,
                                                             p_id};
                                  break;
                                }
                                case 3: {
                                  AcousticSolverAlgorithm<3>{AcousticSolverType::Eucclhyd,
                                                             p_mesh,
                                                             bc_descriptor_list,
                                                             rho_id,
                                                             u_id,
                                                             p_id};
                                  break;
                                }
                                default: {
                                  throw UnexpectedError("invalid mesh dimension");
                                }
                                }
                              }

                              ));
}