#ifndef BOUNDARY_CONDITION_DESCRIPTOR_HPP
#define BOUNDARY_CONDITION_DESCRIPTOR_HPP

#include <RefId.hpp>
#include <memory>

class BoundaryDescriptor
{
 public:
  enum class Type
  {
    named,
    numbered
  };

 protected:
  virtual std::ostream& _write(std::ostream& os) const = 0;

 public:
  friend std::ostream& operator<<(std::ostream& os,
                                  const BoundaryDescriptor& bd)
  {
    return bd._write(os);
  }

  virtual bool operator==(const RefId& ref_id) const =0;
  friend bool operator==(const RefId& ref_id, const BoundaryDescriptor& bcd)
  {
    return bcd == ref_id;
  }
  virtual Type type() const = 0;
  BoundaryDescriptor(const BoundaryDescriptor&) = default;
  BoundaryDescriptor() = default;
  virtual ~BoundaryDescriptor() = default;
};

class NamedBoundaryDescriptor
    : public BoundaryDescriptor
{
 private:
  std::string m_name;

  std::ostream& _write(std::ostream& os) const final
  {
    os << '"' << m_name << '"';
    return os;
  }

 public:
  bool operator==(const RefId& ref_id) const final
  {
    return m_name == ref_id.tagName();
  }

  Type type() const final
  {
    return Type::named;
  }

  NamedBoundaryDescriptor(const NamedBoundaryDescriptor&) = default;
  NamedBoundaryDescriptor(const std::string& name)
      : m_name(name)
  {
    ;
  }
  virtual ~NamedBoundaryDescriptor() = default;
};

class NumberedBoundaryDescriptor
    : public BoundaryDescriptor
{
 private:
  unsigned int m_number;

  std::ostream& _write(std::ostream& os) const final
  {
    os << '"' << m_number << '"';
    return os;
  }

  bool operator==(const RefId& ref_id) const final
  {
    return m_number == ref_id.tagNumber();
  }

 public:
  Type type() const final
  {
    return Type::numbered;
  }

  NumberedBoundaryDescriptor(const NumberedBoundaryDescriptor&) = default;
  NumberedBoundaryDescriptor(const unsigned int& number)
      : m_number(number)
  {
    ;
  }
  virtual ~NumberedBoundaryDescriptor() = default;
};


class BoundaryConditionDescriptor
{
 public:
  enum class Type
  {
    symmetry
  };
 protected:
  std::shared_ptr<BoundaryDescriptor> m_boundary_descriptor;

  virtual std::ostream& _write(std::ostream& os) const = 0;

 public:
  friend std::ostream& operator<<(std::ostream& os, const BoundaryConditionDescriptor& bcd)
  {
    return bcd._write(os);
  }

  virtual Type type() const = 0;

  const BoundaryDescriptor& boundaryDescriptor() const
  {
    return *m_boundary_descriptor;
  }

  BoundaryConditionDescriptor(std::shared_ptr<BoundaryDescriptor> boundary_descriptor)
      : m_boundary_descriptor(boundary_descriptor)
  {
    ;
  }

  virtual ~BoundaryConditionDescriptor() = default;
};


class SymmetryBoundaryConditionDescriptor
    : public BoundaryConditionDescriptor
{
 private:
  std::ostream& _write(std::ostream& os) const final
  {
    os << "symmetry(" << *m_boundary_descriptor << ")";
    return os;
  }
 public:
  Type type() const final
  {
    return Type::symmetry;
  }

  SymmetryBoundaryConditionDescriptor(std::shared_ptr<BoundaryDescriptor> boundary_descriptor)
      : BoundaryConditionDescriptor(boundary_descriptor)
  {
    ;
  }

  SymmetryBoundaryConditionDescriptor(const SymmetryBoundaryConditionDescriptor&) = default;
  ~SymmetryBoundaryConditionDescriptor() = default;
};

#endif // BOUNDARY_CONDITION_DESCRIPTOR_HPP