/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#pragma once

/** \file
 * \ingroup fn
 */

#include "FN_multi_function.hh"

namespace blender::fn {

class MFVariable;
class MFInstruction;
class MFCallInstruction;
class MFBranchInstruction;
class MFDestructInstruction;
class MFDummyInstruction;
class MFReturnInstruction;
class MFProcedure;

/** Every instruction has exactly one of these types. */
enum class MFInstructionType {
  Call,
  Branch,
  Destruct,
  Dummy,
  Return,
};

/**
 * An #MFInstructionCursor points to a position in a multi-function procedure, where an instruction
 * can be inserted.
 */
class MFInstructionCursor {
 public:
  enum Type {
    None,
    Entry,
    Call,
    Destruct,
    Branch,
    Dummy,
  };

 private:
  Type type_ = None;
  MFInstruction *instruction_ = nullptr;
  /* Only used when it is a branch instruction. */
  bool branch_output_ = false;

 public:
  MFInstructionCursor() = default;
  MFInstructionCursor(MFCallInstruction &instruction);
  MFInstructionCursor(MFDestructInstruction &instruction);
  MFInstructionCursor(MFBranchInstruction &instruction, bool branch_output);
  MFInstructionCursor(MFDummyInstruction &instruction);

  static MFInstructionCursor ForEntry();

  MFInstruction *next(MFProcedure &procedure) const;
  void set_next(MFProcedure &procedure, MFInstruction *new_instruction) const;

  MFInstruction *instruction() const;

  Type type() const;

  friend bool operator==(const MFInstructionCursor &a, const MFInstructionCursor &b)
  {
    return a.type_ == b.type_ && a.instruction_ == b.instruction_ &&
           a.branch_output_ == b.branch_output_;
  }

  friend bool operator!=(const MFInstructionCursor &a, const MFInstructionCursor &b)
  {
    return !(a == b);
  }
};

/**
 * A variable is similar to a virtual register in other libraries. During evaluation, every is
 * either uninitialized or contains a value for every index (remember, a multi-function procedure
 * is always evaluated for many indices at the same time).
 */
class MFVariable : NonCopyable, NonMovable {
 private:
  MFDataType data_type_;
  Vector<MFInstruction *> users_;
  std::string name_;
  int id_;

  friend MFProcedure;
  friend MFCallInstruction;
  friend MFBranchInstruction;
  friend MFDestructInstruction;

 public:
  MFDataType data_type() const;
  Span<MFInstruction *> users();

  StringRefNull name() const;
  void set_name(std::string name);

  int id() const;
};

/** Base class for all instruction types. */
class MFInstruction : NonCopyable, NonMovable {
 protected:
  MFInstructionType type_;
  Vector<MFInstructionCursor> prev_;

  friend MFProcedure;
  friend MFCallInstruction;
  friend MFBranchInstruction;
  friend MFDestructInstruction;
  friend MFDummyInstruction;
  friend MFReturnInstruction;

 public:
  MFInstructionType type() const;

  /**
   * Other instructions that come before this instruction. There can be multiple previous
   * instructions when branching is used in the procedure.
   */
  Span<MFInstructionCursor> prev() const;
};

/**
 * References a multi-function that is evaluated when the instruction is executed. It also
 * references the variables whose data will be passed into the multi-function.
 */
class MFCallInstruction : public MFInstruction {
 private:
  const MultiFunction *fn_ = nullptr;
  MFInstruction *next_ = nullptr;
  MutableSpan<MFVariable *> params_;

  friend MFProcedure;

 public:
  const MultiFunction &fn() const;

  MFInstruction *next();
  const MFInstruction *next() const;
  void set_next(MFInstruction *instruction);

  void set_param_variable(int param_index, MFVariable *variable);
  void set_params(Span<MFVariable *> variables);

  Span<MFVariable *> params();
  Span<const MFVariable *> params() const;
};

/**
 * What makes a branch instruction special is that it has two successor instructions. One that will
 * be used when a condition variable was true, and one otherwise.
 */
class MFBranchInstruction : public MFInstruction {
 private:
  MFVariable *condition_ = nullptr;
  MFInstruction *branch_true_ = nullptr;
  MFInstruction *branch_false_ = nullptr;

  friend MFProcedure;

 public:
  MFVariable *condition();
  const MFVariable *condition() const;
  void set_condition(MFVariable *variable);

  MFInstruction *branch_true();
  const MFInstruction *branch_true() const;
  void set_branch_true(MFInstruction *instruction);

  MFInstruction *branch_false();
  const MFInstruction *branch_false() const;
  void set_branch_false(MFInstruction *instruction);
};

/**
 * A destruct instruction destructs a single variable. So the variable value will be uninitialized
 * after this instruction. All variables that are not output variables of the procedure, have to be
 * destructed before the procedure ends. Destructing early is generally a good thing, because it
 * might help with memory buffer reuse, which decreases memory-usage and increases performance.
 */
class MFDestructInstruction : public MFInstruction {
 private:
  MFVariable *variable_ = nullptr;
  MFInstruction *next_ = nullptr;

  friend MFProcedure;

 public:
  MFVariable *variable();
  const MFVariable *variable() const;
  void set_variable(MFVariable *variable);

  MFInstruction *next();
  const MFInstruction *next() const;
  void set_next(MFInstruction *instruction);
};

/**
 * This instruction does nothing, it just exists to building a procedure simpler in some cases.
 */
class MFDummyInstruction : public MFInstruction {
 private:
  MFInstruction *next_ = nullptr;

  friend MFProcedure;

 public:
  MFInstruction *next();
  const MFInstruction *next() const;
  void set_next(MFInstruction *instruction);
};

/**
 * This instruction ends the procedure.
 */
class MFReturnInstruction : public MFInstruction {
};

/**
 * Inputs and outputs of the entire procedure network.
 */
struct MFParameter {
  MFParamType::InterfaceType type;
  MFVariable *variable;
};

struct ConstMFParameter {
  MFParamType::InterfaceType type;
  const MFVariable *variable;
};

/**
 * A multi-function procedure allows composing multi-functions in arbitrary ways. It consists of
 * variables and instructions that operate on those variables. Branching and looping within the
 * procedure is supported as well.
 *
 * Typically, a #MFProcedure should be constructed using a #MFProcedureBuilder, which has many more
 * utility methods for common use cases.
 */
class MFProcedure : NonCopyable, NonMovable {
 private:
  LinearAllocator<> allocator_;
  Vector<MFCallInstruction *> call_instructions_;
  Vector<MFBranchInstruction *> branch_instructions_;
  Vector<MFDestructInstruction *> destruct_instructions_;
  Vector<MFDummyInstruction *> dummy_instructions_;
  Vector<MFReturnInstruction *> return_instructions_;
  Vector<MFVariable *> variables_;
  Vector<MFParameter> params_;
  MFInstruction *entry_ = nullptr;

  friend class MFProcedureDotExport;

 public:
  MFProcedure() = default;
  ~MFProcedure();

  MFVariable &new_variable(MFDataType data_type, std::string name = "");
  MFCallInstruction &new_call_instruction(const MultiFunction &fn);
  MFBranchInstruction &new_branch_instruction();
  MFDestructInstruction &new_destruct_instruction();
  MFDummyInstruction &new_dummy_instruction();
  MFReturnInstruction &new_return_instruction();

  void add_parameter(MFParamType::InterfaceType interface_type, MFVariable &variable);

  Span<ConstMFParameter> params() const;

  MFInstruction *entry();
  const MFInstruction *entry() const;
  void set_entry(MFInstruction &entry);

  Span<MFVariable *> variables();
  Span<const MFVariable *> variables() const;

  std::string to_dot() const;

  bool validate() const;

 private:
  bool validate_all_instruction_pointers_set() const;
  bool validate_all_params_provided() const;
  bool validate_same_variables_in_one_call() const;
  bool validate_parameters() const;
  bool validate_initialization() const;

  struct InitState {
    bool can_be_initialized = false;
    bool can_be_uninitialized = false;
  };

  InitState find_initialization_state_before_instruction(const MFInstruction &target_instruction,
                                                         const MFVariable &variable) const;
};

namespace multi_function_procedure_types {
using MFVariable = fn::MFVariable;
using MFInstruction = fn::MFInstruction;
using MFCallInstruction = fn::MFCallInstruction;
using MFBranchInstruction = fn::MFBranchInstruction;
using MFDestructInstruction = fn::MFDestructInstruction;
using MFProcedure = fn::MFProcedure;
}  // namespace multi_function_procedure_types

/* -------------------------------------------------------------------- */
/** \name #MFInstructionCursor Inline Methods
 * \{ */

inline MFInstructionCursor::MFInstructionCursor(MFCallInstruction &instruction)
    : type_(Call), instruction_(&instruction)
{
}

inline MFInstructionCursor::MFInstructionCursor(MFDestructInstruction &instruction)
    : type_(Destruct), instruction_(&instruction)
{
}

inline MFInstructionCursor::MFInstructionCursor(MFBranchInstruction &instruction,
                                                bool branch_output)
    : type_(Branch), instruction_(&instruction), branch_output_(branch_output)
{
}

inline MFInstructionCursor::MFInstructionCursor(MFDummyInstruction &instruction)
    : type_(Dummy), instruction_(&instruction)
{
}

inline MFInstructionCursor MFInstructionCursor::ForEntry()
{
  MFInstructionCursor cursor;
  cursor.type_ = Type::Entry;
  return cursor;
}

inline MFInstruction *MFInstructionCursor::instruction() const
{
  /* This isn't really const correct unfortunately, because to make it correct we'll need a const
   * version of #MFInstructionCursor. */
  return instruction_;
}

inline MFInstructionCursor::Type MFInstructionCursor::type() const
{
  return type_;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name #MFVariable Inline Methods
 * \{ */

inline MFDataType MFVariable::data_type() const
{
  return data_type_;
}

inline Span<MFInstruction *> MFVariable::users()
{
  return users_;
}

inline StringRefNull MFVariable::name() const
{
  return name_;
}

inline int MFVariable::id() const
{
  return id_;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name #MFInstruction Inline Methods
 * \{ */

inline MFInstructionType MFInstruction::type() const
{
  return type_;
}

inline Span<MFInstructionCursor> MFInstruction::prev() const
{
  return prev_;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name #MFCallInstruction Inline Methods
 * \{ */

inline const MultiFunction &MFCallInstruction::fn() const
{
  return *fn_;
}

inline MFInstruction *MFCallInstruction::next()
{
  return next_;
}

inline const MFInstruction *MFCallInstruction::next() const
{
  return next_;
}

inline Span<MFVariable *> MFCallInstruction::params()
{
  return params_;
}

inline Span<const MFVariable *> MFCallInstruction::params() const
{
  return params_;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name #MFBranchInstruction Inline Methods
 * \{ */

inline MFVariable *MFBranchInstruction::condition()
{
  return condition_;
}

inline const MFVariable *MFBranchInstruction::condition() const
{
  return condition_;
}

inline MFInstruction *MFBranchInstruction::branch_true()
{
  return branch_true_;
}

inline const MFInstruction *MFBranchInstruction::branch_true() const
{
  return branch_true_;
}

inline MFInstruction *MFBranchInstruction::branch_false()
{
  return branch_false_;
}

inline const MFInstruction *MFBranchInstruction::branch_false() const
{
  return branch_false_;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name #MFDestructInstruction Inline Methods
 * \{ */

inline MFVariable *MFDestructInstruction::variable()
{
  return variable_;
}

inline const MFVariable *MFDestructInstruction::variable() const
{
  return variable_;
}

inline MFInstruction *MFDestructInstruction::next()
{
  return next_;
}

inline const MFInstruction *MFDestructInstruction::next() const
{
  return next_;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name #MFDummyInstruction Inline Methods
 * \{ */

inline MFInstruction *MFDummyInstruction::next()
{
  return next_;
}

inline const MFInstruction *MFDummyInstruction::next() const
{
  return next_;
}

/** \} */

/* -------------------------------------------------------------------- */
/** \name #MFProcedure Inline Methods
 * \{ */

inline Span<ConstMFParameter> MFProcedure::params() const
{
  static_assert(sizeof(MFParameter) == sizeof(ConstMFParameter));
  return params_.as_span().cast<ConstMFParameter>();
}

inline MFInstruction *MFProcedure::entry()
{
  return entry_;
}

inline const MFInstruction *MFProcedure::entry() const
{
  return entry_;
}

inline Span<MFVariable *> MFProcedure::variables()
{
  return variables_;
}

inline Span<const MFVariable *> MFProcedure::variables() const
{
  return variables_;
}

/** \} */

}  // namespace blender::fn
