/**************************************************************************
 * Perpendicular Laplacian inversion in X-Y
 *
 * Equation solved is:
 *
 * Div( A * Grad_perp(x) ) + B*x = b
 *
 * Intended for use in solving n = 0 component of potential
 * from inversion of vorticity equation
 *
 **************************************************************************
 * Copyright 2015-2025 BOUT++ Team
 *
 * Contact: Ben Dudson, bd512@york.ac.uk
 *
 * This file is part of BOUT++.
 *
 * BOUT++ is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * BOUT++ is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with BOUT++.  If not, see <http://www.gnu.org/licenses/>.
 *
 **************************************************************************/

#ifndef BOUT_LAPLACEXY_H
#define BOUT_LAPLACEXY_H

#include <bout/bout_types.hxx>
#include <bout/field2d.hxx>
#include <bout/generic_factory.hxx>
#include <bout/mesh.hxx>

#include <string>

class LaplaceXY;
class Options;
class Solver;

class LaplaceXYFactory
    : public Factory<LaplaceXY, LaplaceXYFactory, Mesh*, Options*, CELL_LOC> {
public:
  static constexpr auto type_name = "LaplaceXY";
  static constexpr auto section_name = "laplacexy";
  static constexpr auto option_name = "type";
  static constexpr auto default_type = "petsc";

  ReturnType create(Mesh* mesh = nullptr, Options* options = nullptr,
                    CELL_LOC loc = CELL_CENTRE) const {
    return Factory::create(getType(options), mesh, optionsOrDefaultSection(options), loc);
  }
  ReturnType create(const std::string& type, Options* options) const {
    return Factory::create(type, nullptr, options, CELL_CENTRE);
  }

  static void ensureRegistered();
};

template <class DerivedType>
using RegisterLaplaceXY = LaplaceXYFactory::RegisterInFactory<DerivedType>;

using RegisterUnavailableLaplaceXY = LaplaceXYFactory::RegisterUnavailableInFactory;

class LaplaceXY {
public:
  LaplaceXY() = default;
  LaplaceXY(const LaplaceXY&) = default;
  LaplaceXY(LaplaceXY&&) = delete;
  LaplaceXY& operator=(const LaplaceXY&) = default;
  LaplaceXY& operator=(LaplaceXY&&) = delete;
  virtual ~LaplaceXY() = default;

  virtual void setCoefs(const Field2D& A, const Field2D& B) = 0;

  virtual Field2D solve(const Field2D& b, const Field2D& x0) = 0;

  static std::unique_ptr<LaplaceXY> create(Mesh* m = nullptr, Options* opt = nullptr,
                                           CELL_LOC loc = CELL_CENTRE) {
    return LaplaceXYFactory::getInstance().create(m, opt, loc);
  }

protected:
  static const int INVERT_DC_GRAD = 1;
  static const int INVERT_AC_GRAD = 2; // Use zero neumann (NOTE: AC is a misnomer)
  static const int INVERT_SET = 16;    // Set boundary to x0 value
  static const int INVERT_RHS = 32;    // Set boundary to b value
};

#endif // BOUT_LAPLACEXY_H
